日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

java分布式锁工具类_java 通过redis实现分布式锁

發(fā)布時(shí)間:2023/12/10 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java分布式锁工具类_java 通过redis实现分布式锁 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 開(kāi)局

在多線程環(huán)境中,經(jīng)常會(huì)碰到需要加鎖的情況,由于現(xiàn)在的系統(tǒng)基本都是集群分布式部署,JVM的lock已經(jīng)不能滿足分布式要求,分布式鎖就這樣產(chǎn)生了。。。

百度一下,網(wǎng)上有很多分布式鎖的方案或者例子,琳瑯滿目,看了之后不知所措,總體來(lái)說(shuō)有以下幾種:

基于數(shù)據(jù)庫(kù)

基于zookeeper

基于redis

基于memcached

各有優(yōu)缺點(diǎn)和實(shí)現(xiàn)難度,這里就不一一分析。本文主要是基于redis的setnx實(shí)現(xiàn)分布式鎖,比較簡(jiǎn)單有一定的局限性,歡迎大家提出意見(jiàn)建議!

2. 加鎖過(guò)程

執(zhí)行redis的setnx,只有key不存在才能set成功(實(shí)際使用的是set(key, value, "NX", "EX", seconds),redis較新版本支持)

如果set成功(同時(shí)也設(shè)置了key的過(guò)期時(shí)間),則表示加鎖成功

如果set失敗,則每次sleep(x)毫秒后不斷嘗試,直到成功或者超時(shí)

3. 釋放過(guò)程

判斷加鎖是否成功

如果成功,則執(zhí)行redis的del刪除

4. 問(wèn)題思考

加鎖時(shí),鎖的redis key過(guò)期時(shí)間多長(zhǎng)合適?

需要根據(jù)業(yè)務(wù)執(zhí)行的時(shí)間長(zhǎng)度來(lái)評(píng)估,默認(rèn)30秒滿足絕大部分需求,支持動(dòng)態(tài)修改

加鎖時(shí),重試超時(shí)時(shí)間多長(zhǎng)合適?本文設(shè)置的是過(guò)期時(shí)間的1.2倍,目的是在最壞的情況下等待鎖過(guò)期后,盡量保證獲取到鎖,否則拋出超時(shí)異常。這個(gè)設(shè)置不完全合理

加鎖時(shí),重試的sleep時(shí)間多長(zhǎng)合適?本文采用的是隨機(jī)[50-300)毫秒,避免出現(xiàn)大量線程同時(shí)競(jìng)爭(zhēng),目的是錯(cuò)峰吧

釋放時(shí),如何避免釋放了其他線程的鎖(A獲取鎖后由于掛起導(dǎo)致鎖到期自動(dòng)釋放;此時(shí)B獲取到鎖,而A又恢復(fù)運(yùn)行釋放了B的鎖)?在初始化鎖時(shí)生個(gè)一個(gè)唯一字符串,作為redis鎖的value;value一致時(shí)表明是自己的鎖,可以釋放

5. 上代碼!

用法

RedisLock lock = new RedisLock(redisHelper, lockKey);

try {

// 執(zhí)行加鎖,防止并發(fā)問(wèn)題

lock.tryLock();

// do somethings

doSomethings()

}

finally {

// 釋放鎖

lock.release();

}

RedisLock實(shí)現(xiàn)(注:依賴RedisHepler類,僅僅是對(duì)jedis的一層封裝,可自行實(shí)現(xiàn))

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

* RedisLock

*

* @version 2017-9-21上午11:56:27

* @author xiaoyun.zeng

*/

public class RedisLock {

private Logger logger = LoggerFactory.getLogger(getClass());

/**

* key前綴

*/

private static final String PREFIX = "lock:";

/**

* 操作redis的工具類

*/

private RedisHelper redisHelper;

/**

* redis key

*/

private String redisKey = null;

/**

* redis value

*/

private String redisValue = null;

/**

* 鎖的過(guò)期時(shí)間(秒),默認(rèn)30秒,防止線程獲取鎖后掛掉無(wú)法釋放鎖

*/

private int lockExpire = 30;

/**

* 嘗試加鎖超時(shí)時(shí)間(毫秒),默認(rèn)為expire的1.2倍

*/

private int tryTimeout = lockExpire * 1200;

/**

* 嘗試加鎖次數(shù)計(jì)數(shù)器

*/

private long tryCounter = 0;

/**

* 加鎖成功標(biāo)記

*/

private boolean success = false;

private long startMillis = 0;

private long expendMillis = 0;

/**

* RedisLock

*

* @param redisHelper

* @param lockKey

*/

public RedisLock(RedisHelper redisHelper, String lockKey) {

this.redisHelper = redisHelper;

this.redisKey = PREFIX + lockKey;

// 生成redis value,用于釋放鎖時(shí)比對(duì)是否屬于自己的鎖

// 生成規(guī)則 lockKey+時(shí)間戳+隨機(jī)數(shù),避免重復(fù)

// 樂(lè)觀地認(rèn)為:

// 1、同一毫秒內(nèi),隨機(jī)數(shù)相同的概率極小

// 2、釋放非自己線程鎖的幾率極小(release方法有說(shuō)明這種情況)

this.redisValue = lockKey + "-" + System.currentTimeMillis() + "-" + this.random(10000);

}

/**

* RedisLock

*

* @param redisHelper

* @param lockKey

* @param expire

*/

public RedisLock(RedisHelper redisHelper, String lockKey, int lockExpire) {

this(redisHelper, lockKey);

// 過(guò)期時(shí)間

this.lockExpire = lockExpire;

// 超時(shí)時(shí)間(毫秒),默認(rèn)為expire的1.2倍

this.tryTimeout = lockExpire * 1200;

}

/**

* 嘗試加鎖

*

* 嘗試加鎖的過(guò)程將會(huì)一直阻塞下去,直到加鎖成功或超時(shí)

*

* @version 2017-9-21下午12:00:07

* @author xiaoyun.zeng

* @return

*/

public void tryLock() throws RuntimeException {

startMillis = System.currentTimeMillis();

// 首次直接請(qǐng)求加鎖

if (!lock()) {

do {

// 超時(shí)判斷,避免永遠(yuǎn)獲取不到鎖的情況下,一直嘗試

// 超時(shí)拋出runtime異常

if (System.currentTimeMillis() - startMillis >= tryTimeout) {

throw new RuntimeException("嘗試加鎖超時(shí)" + tryTimeout + "ms");

}

// 隨機(jī)休眠[50-300)毫秒

// 避免出現(xiàn)大量線程同時(shí)競(jìng)爭(zhēng)

try {

Thread.sleep(this.random(250) + 50);

}

catch (InterruptedException e) {

// 出現(xiàn)異常直接拋出

throw new RuntimeException(e);

}

}

while (!lock());

}

}

/**

* 釋放鎖

*

* @version 2017-9-21下午12:00:21

* @author xiaoyun.zeng

* @param lockKey

*/

public void release() {

// 加鎖成功才執(zhí)行釋放

if (success) {

// 釋放前,檢查redis value是否一致

// 避免A獲取鎖后由于掛起導(dǎo)致鎖到期自動(dòng)釋放

// 此時(shí)B獲取到鎖,而A又恢復(fù)運(yùn)行釋放了B的鎖

String value = redisHelper.get(redisKey);

if (redisValue.equals(value)) {

redisHelper.del(redisKey);

logger.debug("已釋放鎖:{}", redisValue);

}

}

}

/**

* 加鎖

*

* @version 2017-9-21下午6:25:58

* @author xiaoyun.zeng

* @param key

* @param value

* @param lockExpire

* @return

*/

private boolean lock() {

// 加鎖計(jì)數(shù)器+1

tryCounter++;

// 調(diào)用redis setnx完成加鎖,返回true表示加鎖成功,否則失敗

success = redisHelper.setNx(redisKey, redisValue, lockExpire);

// 計(jì)算總耗時(shí)

expendMillis = System.currentTimeMillis() - startMillis;

// 記錄日志

if (success) {

logger.debug("加鎖成功:嘗試{}次,耗時(shí){}ms,{}", tryCounter, expendMillis, redisValue);

}

return success;

}

/**

* 產(chǎn)生隨機(jī)數(shù)

*

* @version 2017-9-22上午10:05:52

* @author xiaoyun.zeng

* @param max

* @return

*/

private int random(int max) {

return (int) (Math.random() * max);

}

}

6. 測(cè)試代碼

單元測(cè)試:

@RunWith(SpringRunner.class)

@SpringBootTest

public class RedisLockTest {

@Autowired

private RedisHelper redisHelper;

@Test

public void test() {

for (int i = 0; i < 10; i++) {

new Thread(new Runnable() {

@Override

public void run() {

RedisLock lock = new RedisLock(redisHelper, "zxy");

try {

lock.tryLock();

try {

Thread.sleep(2 * 1000);

}

catch (InterruptedException e) {

e.printStackTrace();

}

}

finally {

lock.release();

}

}

}).start();

}

while(true) {

}

}

}

日志輸出:

2017/10/12 17:47:28.335 [Thread-8] DEBUG [RedisLock.161] 加鎖成功:嘗試1次,耗時(shí)4ms,zxy-1507801648330-6665

2017/10/12 17:47:30.340 [Thread-8] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648330-6665

2017/10/12 17:47:30.351 [Thread-14] DEBUG [RedisLock.161] 加鎖成功:嘗試12次,耗時(shí)2018ms,zxy-1507801648333-6866

2017/10/12 17:47:32.356 [Thread-14] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648333-6866

2017/10/12 17:47:32.396 [Thread-11] DEBUG [RedisLock.161] 加鎖成功:嘗試22次,耗時(shí)4065ms,zxy-1507801648331-5217

2017/10/12 17:47:34.400 [Thread-11] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648331-5217

2017/10/12 17:47:34.430 [Thread-12] DEBUG [RedisLock.161] 加鎖成功:嘗試39次,耗時(shí)6098ms,zxy-1507801648332-7708

2017/10/12 17:47:36.433 [Thread-12] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648332-7708

2017/10/12 17:47:36.453 [Thread-17] DEBUG [RedisLock.161] 加鎖成功:嘗試50次,耗時(shí)8119ms,zxy-1507801648334-2362

2017/10/12 17:47:38.457 [Thread-17] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648334-2362

2017/10/12 17:47:38.494 [Thread-9] DEBUG [RedisLock.161] 加鎖成功:嘗試57次,耗時(shí)10164ms,zxy-1507801648330-7086

2017/10/12 17:47:40.497 [Thread-9] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648330-7086

2017/10/12 17:47:40.587 [Thread-13] DEBUG [RedisLock.161] 加鎖成功:嘗試70次,耗時(shí)12254ms,zxy-1507801648333-8881

2017/10/12 17:47:42.590 [Thread-13] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648333-8881

2017/10/12 17:47:42.611 [Thread-15] DEBUG [RedisLock.161] 加鎖成功:嘗試82次,耗時(shí)14276ms,zxy-1507801648335-2509

2017/10/12 17:47:44.614 [Thread-15] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648335-2509

2017/10/12 17:47:44.699 [Thread-16] DEBUG [RedisLock.161] 加鎖成功:嘗試89次,耗時(shí)16365ms,zxy-1507801648334-5791

2017/10/12 17:47:46.702 [Thread-16] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648334-5791

2017/10/12 17:47:46.802 [Thread-10] DEBUG [RedisLock.161] 加鎖成功:嘗試106次,耗時(shí)18471ms,zxy-1507801648331-7347

2017/10/12 17:47:48.805 [Thread-10] DEBUG [RedisLock.137] 已釋放鎖:zxy-1507801648331-7347

總結(jié)

以上是生活随笔為你收集整理的java分布式锁工具类_java 通过redis实现分布式锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。