1. 集群下的线程并发安全问题
当项目形成集群时,每个 JVM 有一个锁监视器,这些锁监视器彼此间不互通,因此会出现集群下的线程并发安全问题,如下图所示:
2. 分布式锁
分布式锁是解决集群下的线程安全问题的方案,其总体思路就是指通过设置一个全局的锁监视器,让每个集群都能看见。如下图所示:
分布式锁是满足分布式系统或集群模式下的互斥的锁,分布式锁的特性如下:
2.1 分布式锁的实现
3. Redis 分布式锁思路
Redis 实现分布式锁只需要两个基本方法:
- 获取锁:
(1)互斥:确保只能有一个线程获取到锁
(2)非阻塞:尝试一次,成功返回 true,失败返回 false
# nx 表示互斥 ex 是设置超时时间 (防止宕机锁不能被释放)
set lock thread1 ex 10 nx
- 释放锁:
(1)手动释放
del thread1
(2)超时释放
4. Redis 实现分布式锁实现案例 (SpringBoot)
接口:
public interface ILock {
// 获取锁方法
boolean tryLock(long timeoutSec);
// 释放锁方法
boolean unlock();
}
接口实现:
/**
* @Author: WanqingLiu
* @Date: 2022/12/07/21:28
*/
public class SimpleRedisLock implements ILock{
private static final String KEY_PREFIX = "lock:";
private String lockName;
private StringRedisTemplate stringRedisTemplate;
public SimpleRedisLock(String lockName, StringRedisTemplate stringRedisTemplate) {
this.lockName = lockName;
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean tryLock(long timeoutSec) {
// 获取当前线程标识
long threadId = Thread.currentThread().getId();
// 获取锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+lockName, threadId+"", timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success); // 防止自动拆箱时候出现空指针问题
}
@Override
public boolean unlock() {
stringRedisTemplate.delete(KEY_PREFIX + lockName);
return false;
}
}
使用:
// 创建锁对象
SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
boolean isLock = simpleRedisLock.tryLock(1200);
if (!isLock){
// 获取锁失败
createVoucherOrder(voucherId);
} else{
return Result.fail("不允许重复下单");
}
// 获取锁成功
try {
// 完成事务,释放锁
IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy(); // 拿到当前对象的代理对象
return proxy.createVoucherOrder(voucherId); // 当前对象没有事务功能,代理对象才有事务功能
} finally {
simpleRedisLock.unlock();
}