文章内容过长,文末有彩蛋!希望大家多多关注
最近面试总是会被问到有没有用过分布式锁、redis锁,由于平时很少接触到,所以只能很无奈的回答“没有”。回来之后就恶补了一下,本文主要做下记录,通过SpringBoot整合redisson来实现分布式锁,并结合demo测试结果。
首先看下大佬总结的图
下面上代码啦
增加依赖
<!--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>
配置
spring:
# redis
redis:
host: 47.103.5.190
port: 6379
redisson:
# 配置单点模式
config: classpath:redisson-single-dev.yml
增加 redisson-single-dev.yml 配置
# 单节点配置
singleServerConfig:
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
pingTimeout: 1000
# 连接超时,单位:毫秒
connectTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 命令失败重试次数,如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。
# 如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。
retryAttempts: 3
# 命令重试发送时间间隔,单位:毫秒
retryInterval: 1500
# 重新连接时间间隔,单位:毫秒
reconnectionTimeout: 3000
# 执行失败最大次数
failedAttempts: 3
# 密码
password: null
# 单个连接最大订阅数量
subscriptionsPerConnection: 5
# 客户端名称
clientName: null
# 节点地址
address: redis://47.103.5.190:6379
# 发布和订阅连接的最小空闲连接数
subscriptionConnectionMinimumIdleSize: 1
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
connectionPoolSize: 64
# 数据库编号
database: 0
# DNS监测时间间隔,单位:毫秒
dnsMonitoringInterval: 5000
# 线程池数量,默认值: 当前处理核数量 * 2
threads: 0
# Netty线程池数量,默认值: 当前处理核数量 * 2
nettyThreads: 0
# 编码
codec: !<org.redisson.codec.JsonJacksonCodec> {}
# 传输模式
transportMode : "NIO"
封装工具类
/**
* redis分布式锁帮助类
* @author yangzhilong
*
*/
@Component
public class RedisLockUtil {
@Autowired
private DistributedLocker locker;
private static DistributedLocker distributedLocker;
@PostConstruct
private void init() {
distributedLocker = locker;
}
/**
* 加锁
* @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);
}
/**
* 带超时的锁
* @param lockKey
* @param timeout 超时时间 单位:秒
*/
public static RLock lock(String lockKey, int timeout) {
return distributedLocker.lock(lockKey, timeout);
}
/**
* 带超时的锁
* @param lockKey
* @param unit 时间单位
* @param timeout 超时时间
*/
public static RLock lock(String lockKey, int timeout,TimeUnit unit ) {
return distributedLocker.lock(lockKey, unit, timeout);
}
/**
* 尝试获取锁
* @param lockKey
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
}
/**
* 尝试获取锁
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
}
}
底层封装:
/**
* @author gourd
*/
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);
}
/**
* @author gourd
*/
@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();
}
}
测试
模拟并发测试
/**
* redis分布式锁控制器
* @author gourd
* @since 2019-07-30
*/
@RestController
@Api(tags = "redisson", description = "redis分布式锁控制器" )
@RequestMapping("/redisson" )
@Slf4j
public class RedissonLockController {
/**
* 锁测试共享变量
*/
private Integer lockCount = 10;
/**
* 无锁测试共享变量
*/
private Integer count = 10;
/**
* 模拟线程数
*/
private static int threadNum = 10;
/**
* 模拟并发测试加锁和不加锁
* @return
*/
@GetMapping("/test")
@ApiOperation(value = "模拟并发测试加锁和不加锁")
public void lock(){
// 计数器
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();
}
/**
* 加锁测试
*/
private void testLockCount() {
String lockKey = "lock-test";
try {
// 加锁,设置超时时间2s
RedisLockUtil.lock(lockKey,2, TimeUnit.SECONDS);
lockCount--;
log.info("lockCount值:"+lockCount);
}catch (Exception e){
log.error(e.getMessage(),e);
}finally {
// 释放锁
RedisLockUtil.unlock(lockKey);
}
}
/**
* 无锁测试
*/
private void testCount() {
count--;
log.info("count值:"+count);
}
public class MyRunnable implements Runnable {
/**
* 计数器
*/
final CountDownLatch countDownLatch;
public MyRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
// 阻塞当前线程,直到计时器的值为0
countDownLatch.await();
} catch (InterruptedException e) {
log.error(e.getMessage(),e);
}
// 无锁操作
testCount();
// 加锁操作
testLockCount();
}
}
}
调用接口后打印值:
测试结果
根据打印结果可以明显看到,未加锁的count--后值是乱序的,而加锁后的结果和我们预期的一样。由于条件问题没办法测试分布式的并发。只能模拟单服务的这种并发,但是原理是一样,希望对大家有帮助。如有错误之处,欢迎指正。
来源:csdn
需要《深入实践springboot.pdf》资料,多种资料及面试题,私信“资料”获取
还有面试宝典《Java核心知识点整理.pdf》“,覆盖了JVM、锁、高并发、反射、Spring原理等多种面试资料!!