为了让同一时刻资源只能一个线程访问,也就是互斥访问共享资源,在单机环境中,我们通常会使用JVM本地锁、volatile、concurrent并发包等方式实现。但随着系统规模的扩大,系统升级成了分布式系统,一个服务会部署到不同服务器上,不同机器不同进程,就需要在多进程下保证线程的安全性了。因此,分布式锁应运而生。
分布式锁的基本原理:
所以使用redis加锁的流程为:
// 加锁
SETNX lock_key 1
// 1分钟后过期
EXPIRE lock_key 60
// 业务逻辑
DO THINGS
// 释放锁
DEL lock_key
但这个操作并不是原子性的,如果加锁过后,Redis宕机,锁就一直释放不了,就会造成死锁。所以最好把这段命令使用Lua脚本,或者使用Redis给我们提供的SETNX和EXPIRE合并的命令SET key value EX seconds NX。SET lock_key 1 EX 60 NX
不足:
Redis虽然可以完成分布式锁的实现,但是还有有不可重入、非阻塞等缺点,使用Redission就给我们大大降低了使用成本,Redission使用大量的Lua脚本和Netty,解决了Redis锁不可重入、非阻塞等缺点。
<!-- redisson 分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.2</version>
</dependency>
Redisson支持单点模式、主从模式、哨兵模式、集群模式,这里以单点模式为例,模拟5个线程同时抢1个红包:
public void test() throws InterruptedException {
String key = "red_paper_id";
long waitTimeout = 100; //100毫秒
long leaseTime = 2000; //2000毫秒
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
String threadName = Thread.currentThread().getName();
RLock lock = redissonClient.getLock(key);
try {
/* waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
* leaseTime 锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)*/
boolean res = lock.tryLock(waitTimeout, leaseTime, TimeUnit.MILLISECONDS);
if(res){
System.out.println(threadName + "获取到锁");
Thread.sleep(1000); //模拟业务处理
boolean res2 = lock.tryLock(waitTimeout, leaseTime, TimeUnit.MILLISECONDS);
if(res2){
System.out.println(threadName + "重新获取锁成功");
} else {
System.out.println(threadName + "重新获取锁失败");
}
//释放锁
lock.unlock();
System.out.println(threadName + "释放锁");
} else {
System.out.println(threadName + "没有获取到锁");
}
} catch (Exception e) {
System.out.println(threadName + "获取锁失败");
}
}, "线程" + i).start();
}
//等待所有线程执行完
Thread.sleep(3000);
}
执行结果:
线程1获取到锁
线程2没有获取到锁
线程4没有获取到锁
线程3没有获取到锁
线程5没有获取到锁
线程1重新获取锁成功
线程1释放锁
可以看到Redission将原子性的操作都封装起来了,我们只需要调用tryLock尝试获取锁并设置过期时间,并且再次tryLock时能够重入获取到锁;其他线程实例也能在给定时间内重试获取锁。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。