面試時(shí)經(jīng)常會(huì)問(wèn)到有沒(méi)有用過(guò)分布式鎖、redis 鎖,很多人平時(shí)很少接觸到。所以只能很無(wú)奈的回答 “沒(méi)有”。本文通過(guò) Spring Boot 整合 redisson 來(lái)實(shí)現(xiàn)分布式鎖,并結(jié)合 demo 測(cè)試結(jié)果。本來(lái)內(nèi)容來(lái)自公眾號(hào)“Java團(tuán)長(zhǎng)”,本人參考文章本地創(chuàng)建springboot項(xiàng)目demo進(jìn)行測(cè)試,文章結(jié)尾附測(cè)試結(jié)果截圖。

微信圖片_20200525161825.png
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>
配置信息
server.port:8080
# redis
spring.redis.host:118.126.66.195
spring.redis.port:6379
spring.redis.password:c428cb38d5f260853678922e
# 連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制)
spring.redis.jedis.pool.max-active:100
# 連接池中的最小空閑連接
spring.redis.jedis.pool.max-idle:10
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制)
spring.redis.jedis.pool.max-wait:-1
# 連接超時(shí)時(shí)間(毫秒)
spring.redis.jedis.pool.timeout:5000
#默認(rèn)是索引為0的數(shù)據(jù)庫(kù)
spring.redis.jedis.pool.database:0
配置類
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
/**
* @Created by zl
* @Date 2020/5/25 11:40
* @Description
*/
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
//單節(jié)點(diǎn)
config.useSingleServer().setAddress("redis://" + host + ":" + port);
if (StringUtils.isEmpty(password)) {
config.useSingleServer().setPassword(null);
} else {
config.useSingleServer().setPassword(password);
}
//添加主從配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
// 集群模式配置 setScanInterval()掃描間隔時(shí)間,單位是毫秒, //可以用"rediss://"來(lái)啟用SSL連接
// config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001").addNodeAddress("redis://127.0.0.1:7002");
return Redisson.create(config);
}
}
Redisson 工具類
package com.redis.lock.demo.util;
import com.redis.lock.demo.redis.DistributedLocker;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import java.util.concurrent.TimeUnit;
/**
* @Created by zl
* @Date 2020/5/25 11:52
* @Description
*/
public class RedisLockUtil {
private static DistributedLocker distributedLocker = SpringContextHolder.getBean(DistributedLocker.class);
/**
* 加鎖
*
* @param lockKey
* @return
*/
public static RLock lock(String lockKey) {
return distributedLocker.lock(lockKey);
}
/**
* 釋放鎖
*
* @param lockKey
*/
public static void unlock(String lockKey) {
distributedLocker.unlock(lockKey);
}
/**
* 釋放鎖
*
* @param lock
*/
public static void unlock(RLock lock) {
distributedLocker.unlock(lock);
}
/**
* 帶超時(shí)的鎖
*
* @param lockKey
* @param timeout 超時(shí)時(shí)間 單位:秒
*/
public static RLock lock(String lockKey, int timeout) {
return distributedLocker.lock(lockKey, timeout);
}
/**
* 帶超時(shí)的鎖
*
* @param lockKey
* @param unit 時(shí)間單位
* @param timeout 超時(shí)時(shí)間
*/
public static RLock lock(String lockKey, int timeout, TimeUnit unit) {
return distributedLocker.lock(lockKey, unit, timeout);
}
/**
* 嘗試獲取鎖
*
* @param lockKey
* @param waitTime 最多等待時(shí)間
* @param leaseTime 上鎖后自動(dòng)釋放鎖時(shí)間
* @return
*/
public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
}
/**
* 嘗試獲取鎖
*
* @param lockKey
* @param unit 時(shí)間單位
* @param waitTime 最多等待時(shí)間
* @param leaseTime 上鎖后自動(dòng)釋放鎖時(shí)間
* @return
*/
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
}
/**
* 獲取計(jì)數(shù)器
*
* @param name
* @return
*/
// public static RCountDownLatch getCountDownLatch(String name) {
// return distributedLocker.getCountDownLatch(name);
// }
//
// /**
// * 獲取信號(hào)量
// *
// * @param name
// * @return
// */
// public static RSemaphore getSemaphore(String name) {
// return distributedLocker.getSemaphore(name);
// }
}
底層封裝
package com.redis.lock.demo.redis;
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;
/**
* @Created by zl
* @Date 2020/5/25 11:53
* @Description TODO
*/
public interface DistributedLocker {
RLock lock(String lockKey);
RLock lock(String lockKey, int timeout);
RLock lock(String lockKey, TimeUnit unit, int timeout);
boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);
void unlock(String lockKey);
void unlock(RLock lock);
}
實(shí)現(xiàn)類
package com.redis.lock.demo.redis.impl;
import com.redis.lock.demo.redis.DistributedLocker;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @Created by zl
* @Date 2020/5/25 11:56
* @Description TODO
*/
@Component
public class RedisDistributedLocker implements DistributedLocker {
@Autowired
private
RedissonClient
redissonClient;
@Override
public RLock
lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
@Override
public RLock lock(String lockKey, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return lock;
}
@Override
public RLock
lock(String lockKey, TimeUnit unit, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
@Override
public boolean
tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
@Override
public void
unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
public void unlock(RLock lock) {
lock.unlock();
}
}
SpringContextHolder類
package com.redis.lock.demo.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* Spring的ApplicationContext的持有者,可以用靜態(tài)方法的方式獲取spring容器中的bean
*
* @author zl
* @date 2020年05月25日 下午14:05:11
*/
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextHolder.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
assertApplicationContext();
return applicationContext;
}
@SuppressWarnings("unchecked")
public static <T> T getBean(String beanName) {
assertApplicationContext();
return (T) applicationContext.getBean(beanName);
}
public static <T> T getBean(Class<T> requiredType) {
assertApplicationContext();
return applicationContext.getBean(requiredType);
}
private static void assertApplicationContext() {
if (SpringContextHolder.applicationContext == null) {
throw new RuntimeException("applicaitonContext屬性為null,請(qǐng)檢查是否注入了SpringContextHolder!");
}
}
}
controller測(cè)試類
package com.redis.lock.demo.controller;
import com.redis.lock.demo.util.RedisLockUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @Created by zl
* @Date 2020/5/25 13:45
* @Description
*/
@RestController
@RequestMapping("/redisson")
@Slf4j
public class RedissonLockController {
/**
* 鎖測(cè)試共享變量
*/
private
Integer lockCount = 30;
/**
* 無(wú)鎖測(cè)試共享變量
*/
private Integer count = 30;
/**
* 模擬線程數(shù)
*/
private static int threadNum = 30;
/**
* 模擬并發(fā)測(cè)試加鎖和不加鎖
*
* @return
*/
@GetMapping("/test")
public void lock() {
// 計(jì)數(shù)器
final CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < threadNum; i++) {
MyRunnable myRunnable = new MyRunnable(countDownLatch);
Thread myThread = new Thread(myRunnable);
myThread.start();
}
// 釋放所有線程
countDownLatch.countDown();
}
/**
* 加鎖測(cè)試
*/
private void testLockCount() {
String lockKey = "lock-test";
try {
// 加鎖,設(shè)置超時(shí)時(shí)間2s
RedisLockUtil.lock(lockKey, 2, TimeUnit.SECONDS);
lockCount--;
log.info("lockCount值:" + lockCount);
} catch
(Exception e) {
log.error(e.getMessage(), e);
} finally {
// 釋放鎖
RedisLockUtil.unlock(lockKey);
}
}
/**
* 無(wú)鎖測(cè)試
*/
private void testCount() {
count--;
log.info("count值:" + count);
}
public class MyRunnable implements Runnable {
/**
* 計(jì)數(shù)器
*/
final CountDownLatch countDownLatch;
public MyRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
// 阻塞當(dāng)前線程,直到計(jì)時(shí)器的值為0
countDownLatch.await();
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
// 無(wú)鎖操作
testCount();
// 加鎖操作
testLockCount();
}
}
}
結(jié)果示例圖

image.png
測(cè)試結(jié)果
根據(jù)打印結(jié)果可以明顯看到,未加鎖的 count-- 后值是亂序的,而加鎖后的結(jié)果是按照順序排序的和我們預(yù)期的一樣。