20240302
基于Redis实现分布式锁
1、利用set nx ex获取锁,并设置过期时间,保存线程标识
2、释放锁时先判断线程标示是否与自己一致,一致则删除锁
注意:释放锁时,需将判断与释放这两个操作变成原子性操作,否则仍可能出现误删情况,这种情况如下图所示:
图中线程1在判断锁标识后出现阻塞,并超时释放,释放后线程2能获取锁,当线程1阻塞完成后由于在前面已判断标识的一致性,因此此时线程1能执行释放操作,从而释放线程2,导致误删。
将操作处理为原子操作的方法为使用Lua脚本,其大致形式如下:
if(redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]) end return 0
redis.call函数中有三个参数,第一个为要执行的命令名称,如"get"、"set",第二个为key列表即操作的redis中的key,第三个为其他参数列表,即要执行这条命令所需要的其他参数的列表。要调用这个脚本,则要调用StringRedisTemplate中的execute函数,并传入DefaultRedisScript<T>类型变量、key列表、其他参数(有几个传几个),其中T为执行脚本后返回的数据类型,DefaultRedisScript<T>类型变量初始化如下,使用static代码块保证初始执行时被加载:
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT; static { UNLOCK_SCRIPT = new DefaultRedisScript<>();//初始化 UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));//指定脚本的位置,ClassPathResource指在resources目录下 UNLOCK_SCRIPT.setResultType(Long.class);//返回类型 }
execute执行方式如下:
stringRedisTemplate.execute( UNLOCK_SCRIPT, Collections.singletonList(KEY_PREFIX + name),//Collections.singletonList函数将单个元素转化为List ID_PREFIX + Thread.currentThread().getId());//后面可继续跟参数