我們設(shè)置key的時候那婉,將value設(shè)置為一個隨機(jī)值r,并且存在當(dāng)前線程ThreadLocal国觉。當(dāng)釋放鎖吧恃,也就是刪除key的時候,不是直接刪除麻诀,而是先判斷該key對應(yīng)的value是否等于先前存在當(dāng)前線程的隨機(jī)值痕寓,只有當(dāng)前當(dāng)前線程持有鎖,才刪除該key蝇闭,由于每個客戶端產(chǎn)生的隨機(jī)值是不一樣的呻率,這樣一來就不會誤釋放別的客戶端申請的鎖了
public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
private static OnecachePlugin oneCache;
static {
oneCache = SpringUtil.getBean(OnecachePlugin.class);
}
/**
* 基礎(chǔ)有效時間
*/
private static final int BASE_VAILD_TIME = 10;
/**
* 鎖的基本等待時間10s
*/
private static final int BASE_WAIT_TIME = 4;
/**
* 隨機(jī)數(shù)
*/
private static Random random = new Random();
private static ThreadLocal<Set<String>> threadLocal = new ThreadLocal<>();
private static void currentThreadSleep() throws InterruptedException {
Thread.sleep((long) (200), random.nextInt(5000));
}
public static boolean easyLock(String key) throws Exception {
return easyLock(key, BASE_WAIT_TIME, BASE_VAILD_TIME);
}
//加鎖
public static boolean easyLock(String key, Integer waitTime, Integer expireTime) throws Exception {
if (ObjectUtil.hasEmpty(waitTime)) {
waitTime = BASE_WAIT_TIME;
}
if (ObjectUtil.hasEmpty(expireTime)) {
expireTime = BASE_VAILD_TIME;
}
Long signTime = System.nanoTime();
Long holdTime = TimeUnit.NANOSECONDS.convert(waitTime, TimeUnit.SECONDS);
String state = UUIDGenerator.getUUID();
while ((System.nanoTime() - signTime) < holdTime) {
//從redis獲取key 如果不存在,則將key存入redis
RString rs = oneCache.getRString(key);
//原子性加鎖
if (rs.setnx(state, Time.seconds(expireTime))) {
logger.info(Thread.currentThread().getId()+" 獲取鎖成功! key = "+key);
if(ObjectUtil.hasEmpty(threadLocal.get())){
threadLocal.set(Sets.newHashSet());
}
threadLocal.get().add(state);
return true;
} else {
try {
currentThreadSleep();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
throw new Exception( "系統(tǒng)正忙呻引,稍后重試");
}
/**
* redis簡單分布式鎖,業(yè)務(wù)執(zhí)行完畢之后必須try finally 調(diào)用釋放鎖方法easyUnLock
*
* @param key 鎖
*/
public static void easyUnLock(String key) {
logger.info(Thread.currentThread().getId()+" 準(zhǔn)備解鎖! key = "+key);
//是否是當(dāng)前線程持有鎖礼仗,以免釋放其他線程加的鎖
if (isHoldEasyLock(key)) {
logger.info(Thread.currentThread().getId()+" 開始解鎖! key = "+key);
try {
//從redis刪除key
RString rs = oneCache.getRString(key);
String state = rs.get();
rs.delete();
threadLocal.get().remove(state);
logger.info(Thread.currentThread().getId()+" 解鎖成功! key = "+key);
} catch (Exception e) {
logger.info("RedisLockUtils easyLock解鎖異常 ->" + e);
}
}
}
/**
* redis簡單分布式鎖,判斷線程是否持有鎖
*/
public static boolean isHoldEasyLock(String key) {
if (ObjectUtil.hasEmpty(threadLocal.get())) {
return false;
}
if (threadLocal.get().contains(oneCache.getRString(key).get())) {
return true;
} else {
return false;
}
}
}
實(shí)際上,這樣還是有一點(diǎn)問題逻悠,釋放鎖不是原子性元践,很有可能在查詢完,redis也剛過期童谒,再刪除就把別的線程的鎖釋放了单旁。
image.png
對以上問題解決辦法,就是使用lua腳本饥伊,參考
http://www.reibang.com/p/0e5d592197c1象浑。
至此,還沒有完琅豆,就是過期時間的的問題愉豺,如果高并發(fā)下,某個線程被阻塞茫因,導(dǎo)致超時蚪拦,那么redis過期了,就導(dǎo)致并發(fā)問題了冻押,如果說過期時間設(shè)置太長驰贷,如果服務(wù)重啟了,那么key就釋放不了了翼雀,
因此饱苟,穩(wěn)妥一點(diǎn)的解決辦法,就是鎖續(xù)命狼渊。
就是在加鎖后箱熬,異步起一個線程,每隔幾秒去判斷一下狈邑,redis鎖是否還在或者過期城须,給重新設(shè)置鎖的過期時間,這樣會完美解決上述問題米苹。
具體實(shí)現(xiàn)有現(xiàn)成框架redisson
我們項(xiàng)目并發(fā)量一般糕伐,所以普通加鎖就能滿足