为什么需要分布式锁 在日常开发中,很多业务场景必须保证原子性。举几个例子:支付订单的操作,就不允许同一个订单,被同时支付,否则会产生错误的数据。拍卖一套房子的下单操作,商品只有一个,那只能一个人下单成功。 如果你只有一台服务器,只运行一个Java程序,那么可以使用Java语言自身的一些锁来实现原子性。但如果我们有多台服务器,甚至不同服务器上跑的是不同的语言。那这时候,我们就需要一个跨平台、跨语言的加锁方式。redis就是其中最方便的一种。核心操作和原理 使用redis实现并发锁,主要是靠两个redis的命令:setnx和getset。setnx的作用是,当一个key不存在的时候,给它赋值。如果key存在或赋值失败,都会返回错误。getset的作用是,先获取一个key的值,然后再给这个key赋新的值,该命令有原子性。 那我们的设计思路就是:先用setnx初步获取锁(set当前的时间戳),如果取不到,那么有两种可能,要么是锁被其他线程持有,要么是其他线程使用完锁后,没有正确释放。所以这个时候,我们需要验证这个锁是否过期。就是把setnx的值拿出来(一个时间戳),和当前时间戳求差,看看超时没有(超时时间是自己设定的)如果锁超时了,我们需要释放它,让它能重新工作。但第二步的操作,不是原子性的。可能有多个线程发现这个锁过期了,都想释放它。这时候,就需要getset这个原子操作,来保证只有一个线程成功。核心代码ServicepublicclassRedisLockService{AutowiredprivateRedisServiceredisService;获取一个Redis分布式锁paramlockKey锁的Key,全局不可重复paramlockExpire锁超时时间,单位毫秒returnpublicbooleangetLock(StringlockKey,longlockExpire){StringredisKeyBaseCommonConfig。REDISLOCKKEYlockKey;if(!redisService。setnx(redisKey,String。valueOf(System。currentTimeMillis()))){没有拿到锁,但有可能是上一个加锁的人忘了释放锁。所以下面验证锁是否超时。StringlockStringredisService。getString(redisKey);if(lockStringnull){前面setnx时,该值还存在,现在不存在了。要么是自然过期了,要么是被别人删掉,准备重新加锁了。稳妥起见,这里返回falsereturnfalse;}longtimestampLong。parseLong(lockString);if(System。currentTimeMillis()timestamplockExpire){锁已经超时先get值,再set值。原子操作,确保不会多个线程进入后面的逻辑StringoldTimestampredisService。setGet(redisKey,String。valueOf(System。currentTimeMillis()));if(oldTimestamp!nulloldTimestamp。equals(lockString)){如果get不到值,或者get到的值不是前面取出来那个了,说明这个锁已经被别的线程占用了。第二次锁竞争成功redisService。setExpireMills(redisKey,lockExpire);returntrue;}else{returnfalse;}}else{returnfalse;}}redisService。setExpireMills(redisKey,lockExpire);returntrue;}删除锁,释放锁paramlockKeypublicvoiddelLock(StringlockKey){StringredisKeyBaseCommonConfig。REDISLOCKKEYlockKey;redisService。del(redisKey);}} 上面的代码使用了一个RedisService的类,里面主要是简单封装了一下redis的操作,你可以替换为自己的service。代码如下:ServicepublicclassRedisService{AutowiredprivateStringRedisTemplatestringRedisTemplate;ResourceprivateRedisTemplateString,ObjectredisTemplate;publicBooleansetnx(Stringkey,Stringvalue){ValueOperationsString,StringopsstringRedisTemplate。opsForValue();returnops。setIfAbsent(key,value);}publicStringsetGet(Stringkey,Stringvalue){returnstringRedisTemplate。opsForValue()。getAndSet(key,value);}publicLongincr(Stringkey){returnstringRedisTemplate。opsForValue()。increment(key);}publicLongincr(Stringkey,longval){returnstringRedisTemplate。opsForValue()。increment(key,val);}publicLongdecr(Stringkey){returnstringRedisTemplate。opsForValue()。decrement(key);}publicLongdecr(Stringkey,longval){returnstringRedisTemplate。opsForValue()。decrement(key,val);}publicLongsetPutString(Stringkey,Stringvalue){returnstringRedisTemplate。opsForSet()。add(key,value);}publicBooleansetExist(Stringkey,Stringmember){returnstringRedisTemplate。opsForSet()。isMember(key,member);}publicSetStringsetList(Stringkey){returnstringRedisTemplate。opsForSet()。members(key);}publicLongsetSize(Stringkey){returnstringRedisTemplate。opsForSet()。size(key);}逐渐废弃没有过期时间的Redisput操作paramkeyparamvalueDeprecatedpublicvoidputString(Stringkey,Stringvalue){stringRedisTemplate。opsForValue()。set(key,value);}s为单位。paramkeyparamvalueparamtimepublicvoidputString(Stringkey,Stringvalue,longtime){stringRedisTemplate。opsForValue()。set(key,value,time,TimeUnit。SECONDS);}publicBooleanzPutString(Stringkey,Stringvalue,longtime){returnstringRedisTemplate。opsForValue()。setIfAbsent(key,value,time,TimeUnit。SECONDS);}publicvoidputString(Stringkey,Stringvalue,longtime,TimeUnitunit){stringRedisTemplate。opsForValue()。set(key,value,time,unit);}publicStringgetString(Stringkey){ValueOperationsString,StringopsstringRedisTemplate。opsForValue();returnops。get(key);}publicvoidputObject(Stringkey,Objectvalue,longtime,TimeUnitunit){redisTemplate。opsForValue()。set(key,value,time,unit);}publicObjectgetObject(Stringkey){returnredisTemplate。opsForValue()。get(key);}publicBooleanexist(Stringkey){returnstringRedisTemplate。hasKey(key);}key有效时间paramkeyreturnpublicLongexpire(Stringkey){returnstringRedisTemplate。getExpire(key,TimeUnit。SECONDS);}publicBooleanchangeExpire(Stringkey,longtime){returnstringRedisTemplate。expire(key,time,TimeUnit。SECONDS);}publicBooleandel(Stringkey){returnstringRedisTemplate。delete(key);}publicvoidhset(Stringkey,Stringfield,Stringvalue){HashOperationsString,String,StringopsstringRedisTemplate。opsForHash();ops。put(key,field,value);}publicStringhget(Stringkey,Stringfield){HashOperationsString,String,StringopsstringRedisTemplate。opsForHash();returnops。get(key,field);}publicMapString,Stringhmget(Stringkey){HashOperationsString,String,StringopsstringRedisTemplate。opsForHash();returnops。entries(key);}publicvoidhmset(Stringkey,HashMapString,Stringdata){HashOperationsString,String,StringopsstringRedisTemplate。opsForHash();ops。putAll(key,data);}publicvoidhdel(Stringkey,Stringfield){HashOperationsString,String,StringopsstringRedisTemplate。opsForHash();ops。delete(key,field);}publicvoidsadd(Stringkey,Stringvalue){stringRedisTemplate。opsForSet()。add(key,value);}publicBooleansetIsMember(Stringkey,Stringval){returnstringRedisTemplate。opsForSet()。isMember(key,val);}publicLongttl(Stringkey){returnstringRedisTemplate。getExpire(key);}publicvoidsetExpire(Stringkey,longtime){stringRedisTemplate。expire(key,time,TimeUnit。SECONDS);}publicvoidsetExpireMills(Stringkey,longtime){stringRedisTemplate。expire(key,time,TimeUnit。MILLISECONDS);}publicListStringmget(ListStringkeys){returnstringRedisTemplate。opsForValue()。multiGet(keys);}}交个朋友 以上代码有任何疑问,可以点击右侧边栏联系作者。收费5毛交个朋友,欢迎来撩! 版权声明:《Springboot使用redis的setnx和getset实现并发锁、分布式锁》为CoderBBB作者的原创文章,转载请附上原文出处链接及本声明。 原文链接:https:www。coderbbb。comarticles2