如上图,在最简单使用分布式锁的时候,我们一般获取一个锁对象,对这个对象进行加锁,当执行完业务流程代码后,对分布式锁进行解锁,这样就保证了高并发下程序的锁安全,以及原子性。
如图,当线程1获得锁之后,会执行业务流程代码,将锁的持有时间设置为30秒,同时每隔10秒检查是否还持有锁,如果业务流程代码没有执行完,就会延长持有锁时间再到30秒,直到执行完成释放锁
此时线程2没有获得锁,会进入一个while循环,间歇性尝试加锁,同时也会订阅线程1是否执行完,因为线程1在释放锁后,会发布一个消息
(1)先看redissonLock.lock();
这个加锁代码
点击lockInterruptibly();
方法
点击lockInterruptibly
方法,此时注意这里时间传了-1
点击这个方法,时间传入的是上面的-1
点到最里面,可以看到因为传入的是-1,不会走第一个判断,直接走我标红色的方法
点进去可以看到一段lua脚本,这个lua脚本可以分为三块,每一块以end结束,最后一块没有end,第一块代表获取到分布式锁,第二块代表可重入锁(用的很少,本章不提),第三块代表没有获取到分布式锁
详细解释:
因为传入的KEYS[1]代表的是传入的锁key,ARGV[1]代表的是时间,看下图,可以看到默认的是30秒,ARGV[2]代表的是线程id+唯一标识uuid组合,方便删除锁使用的
所以第一块就是设置一个分布式锁名,同时设置过期时间,以及分布式锁名对应的value,这个设置是方便判断删除锁使用的
当获取到对应锁之后,得到一个future方法,开启一个监听器,监听是否还持有锁,实际就是看门狗的机制
(2)再看获取失败锁的代码,即没有获得本次分布式锁
可以看到当失败的时候,会返回一个pttl的key,同时也会返回剩余时间,即线程1获得的默认时间30s减去已经执行的时候,假如已经执行的时间是5s,那么此次返回的就剩下25s的时间了
此时方法会返回到这,ttl的这个返回值就是25,他会继续尝试加一次锁,如果还是加锁失败,就会执行 if (ttl >= 0) {}
,这个用了juc的信号量,简单理解就是会在这里等待25s,这25s是等待状态,不会占用cpu,当25s过后,就会再次执行while循环
同时,这样的话,就会出现一个问题,在25s内如果线程1已经执行完了呢,难道这个线程2还要等25s后再重新获得锁吗?当然不会啦
如上图,这里有个订阅模式,实际是redis的特性,他会订阅一个频道,频道的名字是锁的名字
看下解锁逻辑,解锁的时候,就会给这个频道发送消息,这样上面就会监听到解锁,也能够继续尝试获取锁了
如上图,当锁过期了之后,就会发布一个订阅消息,要求解锁了
上面第一块代表过期了,就发布
第二块代表不是我锁的,就不能我解锁
第三块判断,必然走到else里,也会发送订阅消息,因为ARGV[3]传的是-1,