您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)Java基于Redis如何實現(xiàn)分布式鎖的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。
分布式鎖可以基于很多種方式實現(xiàn),比如zookeeper、redis...。不管哪種方式,他的基本原理是不變的:用一個狀態(tài)值表示鎖,對鎖的占用和釋放通過狀態(tài)值來標(biāo)識。
一、為什么Redis可以方便地實現(xiàn)分布式鎖
1、Redis為單進(jìn)程單線程模式,采用隊列模式將并發(fā)訪問變成串行訪問,且多客戶端對Redis的連接并不存在競爭關(guān)系。
2、Redis的SETNX命令可以方便的實現(xiàn)分布式鎖。
setNX(SET if Not eXists)
語法:SETNX key value
返回值:設(shè)置成功,返回 1 ;設(shè)置失敗,返回 0 。
當(dāng)且僅當(dāng) key 不存在時將 key 的值設(shè)為 value,并返回1;若給定的 key 已經(jīng)存在,則 SETNX 不做任何動作,并返回0。
綜上所述,可以通過setnx的返回值來判斷是否獲取到鎖,并且不用擔(dān)心并發(fā)訪問的問題,因為Redis是單線程的,所以如果返回1則獲取到鎖,返回0則沒獲取到。當(dāng)業(yè)務(wù)操作執(zhí)行完后,一定要釋放鎖,釋放鎖的邏輯很簡單,就是把之前設(shè)置的key刪除掉即可,這樣下次又可以通過setnx該key獲取到鎖了。
二、分布式鎖實現(xiàn)
我們已經(jīng)知道可以通過Redis自帶的函數(shù)setNX來實現(xiàn)分布式鎖,具體實現(xiàn)步驟如下。
我在一臺CentOS7的linux虛擬機中安裝了Redis服務(wù),ip地址為:192.168.246.130,服務(wù)端口為:6379。
下面是java通過redis實現(xiàn)分布式鎖的例子:
import redis.clients.jedis.Jedis; public class RedisLock { //鎖的key private static final String key = "DistributedRedisLock"; private static Integer count = 0; public static void main(String[] args) { for(int i=0;i<1000;i++){ new Thread(new Runnable() { @Override public void run() { //獲取Redis連接 Jedis jedis = new Jedis("192.168.246.130", 6379); try{ while(true){ //獲取鎖 if(jedis.setnx(key, Thread.currentThread().getName()) == 1){ try{ System.out.println("線程("+Thread.currentThread().getName()+")獲取到鎖,開始執(zhí)行操作"); count++; System.out.println(count); break; }finally{ System.out.println("操作執(zhí)行完成,釋放鎖"); //操作執(zhí)行完一定要釋放鎖,所以在finally塊中執(zhí)行 jedis.del(key); } }else{ //返回的不是1,說明已經(jīng)有某個線程獲取到了鎖 try { //等待100毫秒之后重試 Thread.sleep(100l); } catch (InterruptedException e) { e.printStackTrace(); } } } }catch(Exception e){ e.printStackTrace(); }finally{ //釋放Redis連接 jedis.disconnect(); } } }).start(); } } }
上述代碼的輸出結(jié)果為:
線程(Thread-320)獲取到鎖,開始執(zhí)行操作 1 操作執(zhí)行完成,釋放鎖 線程(Thread-463)獲取到鎖,開始執(zhí)行操作 2 操作執(zhí)行完成,釋放鎖 線程(Thread-997)獲取到鎖,開始執(zhí)行操作 3 操作執(zhí)行完成,釋放鎖 ... 線程(Thread-409)獲取到鎖,開始執(zhí)行操作 998 操作執(zhí)行完成,釋放鎖 線程(Thread-742)獲取到鎖,開始執(zhí)行操作 999 操作執(zhí)行完成,釋放鎖 線程(Thread-286)獲取到鎖,開始執(zhí)行操作 1000 操作執(zhí)行完成,釋放鎖
上述代碼雖然是在單應(yīng)用多線程情況下測試的,但即便是在分布式環(huán)境下多應(yīng)用多線程去獲取鎖,結(jié)果依然是正確的。
三、解決死鎖問題
之前的例子代碼只是測試代碼,只是為了說明原理,例子本身很簡單,所以有一些考慮不周的地方。比如當(dāng)獲取到鎖之后在業(yè)務(wù)操作執(zhí)行過程中發(fā)生了環(huán)境問題導(dǎo)致斷開了和Redis的連接,那就無法在finally塊中釋放鎖,導(dǎo)致其他等待獲取鎖的線程無限等待下去,也就是發(fā)生了死鎖現(xiàn)象。
解決方式:
可以在Redis中給鎖設(shè)置一個過期時間,這樣即便無法釋放鎖,鎖也能在一段時間后自動釋放。
代碼上只需要在獲取到鎖之后在try語句塊中加入如下代碼:
jedis.expire(key, 10); //這里給鎖設(shè)置10秒的過期時間
更妥善的解決方式:
第一個解決方式并不是很好,因為當(dāng)業(yè)務(wù)操作處理時間很長,超過了設(shè)置的過期時間,那鎖就自動釋放了,然后再執(zhí)行finally塊中釋放鎖的操作時,這個鎖可能已經(jīng)被其他線程所持有,會導(dǎo)致把其他線程持有的鎖給釋放了,從而導(dǎo)致并發(fā)問題。所以更妥善一點的方式是在釋放鎖時判斷一下鎖是否已經(jīng)過期,如果已經(jīng)過期就不用再釋放了。
代碼上把獲取到鎖之后的操作改為如下代碼:
long start = System.currentTimeMillis(); //獲取起始時間毫秒數(shù) try{ jedis.expire(key, 10); ... }finally{ ... if(System.currentTimeMillis() < start+10*1000){ //如果之前設(shè)置的鎖還未過期,則釋放掉 jedis.del(key); } }
感謝各位的閱讀!關(guān)于Java基于Redis如何實現(xiàn)分布式鎖就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。