前言
Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站: https://www.runoob.com/lua/lua-tutorial.html
问题分析
public void unlock() {
// 1.获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 2.获取锁中的标识
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
// 3.判断线程标识和锁标识是否一样
if (threadId.equals(id)) {
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
上述代码中,线程1如果判断完线程标识和锁中的标识一致,进入了If判断中,在此时JVM进行GC垃圾回收,造成阻塞,如果在阻塞时间内,锁到了自动释放时间,被自动释放了,线程2拿到锁后,开始执行代码,此时,线程1的JVM垃圾回收结束,线程1此时就会将线程2的锁释放掉,造成了线程安全问题,解决此问题,就需要将判断和释放锁的动作放到一起执行。
Lua脚本
释放锁的业务流程是这样的:
- 获取锁中的线程标示
- 判断是否与指定的标示(当前线程标示)一致
- 如果一致则释放锁(删除)
- 如果不一致则什么都不做
如果用Lua脚本来表示则是这样的:
-- 这里的 KEYS[1] 就是锁的key,这里的ARGV[1] 就是当前线程标示
-- 获取锁中的标示,判断是否与当前线程标示一致
if(redis.call('GET',KEYS[1]==ARGV[1])) then
-- 一致 删除锁
return redis.call('del',KEYS[1])
end
-- 不一致直接返回0
return 0
java执行Lua脚本
在resources下新建Lua脚本
如果IDEA新建时候没有Lua File选项,则需要先安装Lua插件,安装过的忽略。
RedisTemplate调用Lua脚本的API
修改SimpleRedisLock类的 unlock方法
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
// 使用静态代码块,当类加载时候,就将Lua脚本加载进来,避免重复IO流,影响性能
static {
UNLOCK_SCRIPT=new DefaultRedisScript<>();
// 设置脚本的位置
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
// 设置脚本的返回值
UNLOCK_SCRIPT.setResultType(Long.class);
}
@Override
public void unlock() {
// 调用Lua脚本,释放锁
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),// Collections.singletonList()将单个元素转换成集合。
ID_PREFIX + Thread.currentThread().getId()
);
}