溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java基于Redis如何實現(xiàn)分布式鎖

發(fā)布時間:2020-10-15 17:25:11 來源:億速云 閱讀:166 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(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é)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(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)容。

AI