基于SETNX
如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。
import redis.clients.jedis.Jedis; public class SetNxExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); String key = "my_key"; String value = "my_value"; // 使用 SETNX 命令 if (jedis.setnx(key, value)== 1) {try{ //处理业务 }catch(){ }finally{ jedis.del(key); //释放锁 } } // 关闭连接 jedis.close(); } }
这里我们实现了简单的互斥锁,但是有个问题,如果持有锁的线程挂掉,锁将一直存在,导致死锁。
天才的你肯定想到了,只要给这个key设置一个过期时间就可以。我们通过Expire(key,time)命令设置过期时间,在加锁的时候同时设置一个过期时间。改进代码:
import redis.clients.jedis.Jedis; public class SetNxExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); String key = "my_key"; String value = "my_value"; // 使用 SETNX 命令 if (jedis.setnx(key, value)== 1) { jedis.expire(key , 100); try{ //处理业务 }catch(){ }finally{ jedis.del(key); //释放锁 } } // 关闭连接 jedis.close(); } }
设置了时间解决了死锁问题,但是这里又出现新问题了,设置key和设置过期时间不是原子操作,如果key设置成功,但是过期时间设置失败了,又出现死锁了。另外如果线程A执行时间过长,key到期了释放锁,这个时候线程B拿到锁,此后线程A执行结束,释放锁,将B的锁释放了。
解决方法:
将当前线程id或者使用uuid设为value值,这样在释放的时候可以判断是否是当前线程持有的锁。使用SET的扩展方式使设置key和设置过期时间的具有原子性。
String uuid = String uuid = UUID.randomUUID().toString(); if("OK".equals(jedis.set(key, uuid,newSetParams().nx().ex(100)))){ //加锁 try { //业务处理 }catch(){ }finally{ if(uuid.eauls(jedis.get(key))){ jedis.del(key); //释放锁 } } }
这里if判断和释放锁不是原子操作,可能会释放其他线程的锁.使用lua脚本解决.
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
改写后的代码:
String uuid = String uuid = UUID.randomUUID().toString(); if("OK".equals(jedis.set(key, uuid,newSetParams().nx().ex(100)))){ //加锁 try { //业务处理 }catch(){ }finally{ String luaSript = "" + "if redis.call('get', KEYS[1]) == ARGV[1] then\n" + " return redis.call('del', KEYS[1])\n" + " else\n" + " return 0\n" + " end"; jedis.eval(luaSript, Collections.singletonList(key),Collections.singletonList(value)); } }
以上方案可以基于redis的SETNX实现分布式锁。标签:String,实现,Reids,uuid,value,jedis,key,线程,分布式 From: https://www.cnblogs.com/LH-up-blogs/p/18344116