一、分布式锁的模型
(一)悲观锁: 认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。例如Synchronized、Lock都属于悲观锁。
- 优点: 简单粗暴
- 缺点: 性能略低
(二)乐观锁: 认为线程安全问题不一定会发生,因此不加锁,只有在更新数据时判断有没有其他线程对数据做了修改,如果没有修改则认为是安全的,自己才能更新数据;如果已经被其它线程修改,说明发生了安全问题,此时可以重试或异常。
- 优点: 性能好
- 缺点: 存在成功率低的问题
(三)常见的实现方式:
- 版本号法: 通过 id-stock-version结构,通过查询的version与本次是否相同来判断是否被修改。
- CAS法: 是版本号法的改良版,是用 old-query-new的结构,通过第一次query出来的stock,与第二次提交的stock是否一致,若一致则 old = new;
二、Redis的分布式锁
(一)分布式锁的作用: 作为公用JVM锁监视器,集群中的每台JVM都能获取锁监视器监测的线程,多个JVM内部同步了线程执行。
(二)分布式锁的需求: 多进程可见、互斥、高可用、 高性能、安全性…
(三)常见分布式锁的差异:
MySQL | Redis | Zookeeper | |
---|---|---|---|
互斥 | 利用mysql本身的互斥锁机制 | 利用setnx这样的互斥命令 | 利用节点的唯一性和有序性实现互斥 |
高可用 | 好 | 好 | 好 |
高性能 | 一般 | 好 | 一般 |
安全性 | 断开连接,自动释放锁 | 利用锁超时时间,到期释放 | 临时节点,断开连接自动释放 |
(四)Redis实现分布式锁
1、获取锁:
- 互斥: 确保只有一个线程获取锁。
SETNX lock thread1
2、释放锁
- 手动释放:
DEL lock
- 过期释放:
EXPIRE lock 5
(1)通过 SET 操作 实现原子性操作: SET lock thread1 EX 10 NX
,意思是创建一个lock缓存,值为thread1,保持10s时间,NX为互斥操作
(2)当锁获取失败时的方法:
- 阻塞式获取锁: 一直等待到有线程释放锁。(对CPU资源消耗高)
- 非阻塞式获取锁: 失败就不再尝试获取锁。
3、代码实现分布式锁
(1)需求: 定义一个类,实现Redis分布式锁功能。
public class SimpleRedisLock implements ILockService {
private static final String LOCK = "lock:";
private String threadName;
private StringRedisTemplate redisTemplate;
public SimpleRedisLock() {
}
public SimpleRedisLock(String threadName, StringRedisTemplate redisTemplate) {
this.threadName = threadName;
this.redisTemplate = redisTemplate;
}
@Override
public boolean