在学习Redis的分布式锁时,课中说明需要使用Lua脚本编写setnx来实现分布式锁,而不把setnx直接写在Java中,是因为在Java代码中会存在极端情况。
我们有两个线程,分别时线程1线程2。我们在设置分布式锁时,已经设置了标识,每个锁都有一个唯一标识。线程1获取了锁,在线程1释放时我们要判断锁标识是不是自己的,是自己的才能释放。但是在判断结束还没释放锁的时候,Java虚拟机触发了Full GC,执行stw,线程1就被卡住了。(当然一般这种情况不会发生)让我们再极端一点,Full GC的时间足够长,撑到了线程1的超时释放,线程1的锁就被自动释放了。
既然线程1的锁被自动释放了,线程2就可以获取到锁。但是足够巧,Full GC在恰当的时间结束了,线程1又自由了。前面我们提到,线程1已经判断了锁标识,所以它不会再判断一遍,于是线程1把属于线程2的锁释放掉,发生了并发问题。
为了避免这个极端的问题,课中引入了lua脚本来编写setnx来保证线程的原子性。
那么问题来了,为什么lua脚本可以保证线程的原子性呢?
在使用lua时,Redis采用了单线程模型,也就是说,当Redis执行lua脚本时,其他客户端的请求都必须等待lua脚本执行完毕后才能执行。这就保证了lua脚本的原子性。