使用SETNX
命令獲取分布式鎖的步驟:
- C1和C2線程同時(shí)檢查時(shí)間戳獲取鎖,執(zhí)行
SETNX
命令并都返回0术幔,此時(shí)鎖仍被C3持有,并且C3已經(jīng)崩潰 - C1
DEL
鎖 - C1 使用
SETNX
命令獲取鎖牌柄,并且成功 - C2
DEL
鎖 - C2 使用
SETNX
命令獲取鎖蒋歌,并且成功 - ERROR : 由于競(jìng)態(tài)條件,C1和C2都獲取到了鎖
幸運(yùn)的是汰蓉,以下面的步驟完全可以避免這種情況發(fā)生绷蹲,看看C4線程如何操作
- C4使用
SETNX
命令獲取鎖 - C3已經(jīng)崩潰但是仍然持有鎖,所以Redis返回0給C4
- C4使用
GET
命令獲取鎖并檢查鎖是否已經(jīng)過(guò)期顾孽,如果沒(méi)有過(guò)期瘸右,則繼續(xù)等待一段時(shí)間并重新重試 - 如果鎖已經(jīng)過(guò)期娇跟,C4嘗試
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
- 利用
GETSET
語(yǔ)法,C4可以檢查舊時(shí)間是否仍然是過(guò)期時(shí)間太颤,如果是苞俘,則獲取鎖 - 如果另一個(gè)客戶端C5率先獲取到鎖,C4執(zhí)行
GETSET
命令后將返回非過(guò)期時(shí)間龄章,然后C4繼續(xù)從頭開(kāi)始重新嘗試獲取鎖吃谣。此操作C4將延長(zhǎng)一點(diǎn)C5獲取到的鎖的過(guò)期時(shí)間,不過(guò)這不是什么大問(wèn)題做裙。
private static String LOCK_PREFIX = "prefix";
public boolean lock(String key) {
String lock = LOCK_PREFIX + key;
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] value = connection.get(lock.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
if (expireTime < System.currentTimeMillis()) {
byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
}