假設程序a獲取到鎖之后需要調用程序b,但是程序b需要使用鎖,
但是這個時候程序a並沒有執行完程序所以不能釋放鎖,但是程序b獲取不到鎖就沒有辦法執行,因此就出現了死鎖
這樣可以使用可重入鎖解決(即判斷是自己的鎖則就可以再次獲取鎖)
existe 判斷鎖是否存在,hset 設置鎖, expire 設置鎖的到期時間 hincrby,設置鎖的重入次數的遞增
可重入鎖加鎖:
1.判斷鎖是否被占用(exists lock),如果沒有(0)被占用則直接獲取鎖(hset lock uuid 1)
2.如果鎖被占用了,則判斷當前鎖是否是當前線程占用了鎖(判斷是不是自己的鎖)(hexists lock uuid),如果是(1)則重入(hincrby lock uuid 1)
3.如果返回0,則說明獲取鎖失敗
最后需要充值過期時間,因為程序a在執行期間很可能lock已經快過期,lock的剩余時間不足以等到b執行,因此最好重置下時間
lua腳本(使用lua腳本可以保證原子性)
if redis.call('exists',KEYS[1]) == 0
then
redis.call('hset',KEYS[1],ARVG[1],1)
redis.call('expire',KEYS[1],ARVG[2])
return 1
elseif redis.call('hexists',KEYS[1],ARVG[1]) == 1
then
redis.call('hincrby', KEYS[1] ARVG[1], 1)
redis.call('expire',KEYS[1],ARVG[2])
retuen 1
else
retuen 0
end
lua腳本優化版
if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARVG[1]) == 1
then
redis.call('hset',KEYS[1],ARVG[1],1)
redis.call('expire',KEYS[1],ARVG[2])
return 1
else
retuen 0
end
KEYS:lock
ARGV: uuid 30
可重入鎖解鎖:
lua腳本
if redis,call('hexists' lock,uuid) == 0
then
return nil
elseif redis.call('hincrby',lock,uuid , -1 ) == 0
then
return redis.call('del' lock)
else
return 0
end
注:StringUtils.isBlank() :判斷字符串是否為空 Collerction.isEmpty判斷的的則是大小好像
代碼實現
新建代碼組件
@Component public class Distributedlock { @Autowired private StringRedisTemplate redisTemplate; public Boolean tryLock(String lockName,String uuid,Integer expire){ //鎖的名稱,uuid標識 到期時間 String script = " if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARVG[1]) == 1" + " then " + " redis.call('hset',KEYS[1],ARVG[1],1)" + " redis.call('expire',KEYS[1],ARVG[2]) " + " return 1 " + " else " + "retuen 0 " + "end"; Boolean flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString()); if (!flag){ try { Thread.sleep(50); tryLock(lockName,uuid,expire); } catch (InterruptedException e) { e.printStackTrace(); } } return true; } public void unlock(String lockName,String uuid){ String script = "lua腳本" + "" + " if redis,call('hexists' KEYS[1],ARVG[1]) == 0" + " then " + " return nil" + " elseif redis.call('hincrby',KEYS[1],ARVG[1] , -1 ) == 0" + " then " + " return redis.call('del' KEYS[1])" + " else " + " return 0" + " end"; Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid); // DefaultRedisScript<>(需要執行的腳本,返回值類型) if (flag == null){ throw new RuntimeException("釋放的鎖不屬於你"); } } }
代碼調用
public void testlock2(){ String uuid = UUID.randomUUID().toString(); Boolean lock = this.distributedlock.tryLock("lock", uuid, 30); if (lock){ String num = this.redisTemplate.opsForValue().get("num"); if (StringUtils.isBlank(num)){ this.redisTemplate.opsForValue().set("num","1"); return; } int i = Integer.parseInt(num);//把字符串轉化為數值 this.redisTemplate.opsForValue().set("num",String.valueOf(++i)); this.distributedlock.unlock("lock",uuid); } }
最后的解鎖應該定義在final里面,從而保證死鎖不會發生