java分布式锁工具类_java 通过redis实现分布式锁
1. 開局
在多線程環(huán)境中,經(jīng)常會(huì)碰到需要加鎖的情況,由于現(xiàn)在的系統(tǒng)基本都是集群分布式部署,JVM的lock已經(jīng)不能滿足分布式要求,分布式鎖就這樣產(chǎn)生了。。。
百度一下,網(wǎng)上有很多分布式鎖的方案或者例子,琳瑯滿目,看了之后不知所措,總體來說有以下幾種:
基于數(shù)據(jù)庫
基于zookeeper
基于redis
基于memcached
各有優(yōu)缺點(diǎn)和實(shí)現(xiàn)難度,這里就不一一分析。本文主要是基于redis的setnx實(shí)現(xiàn)分布式鎖,比較簡(jiǎn)單有一定的局限性,歡迎大家提出意見建議!
2. 加鎖過程
執(zhí)行redis的setnx,只有key不存在才能set成功(實(shí)際使用的是set(key, value, "NX", "EX", seconds),redis較新版本支持)
如果set成功(同時(shí)也設(shè)置了key的過期時(shí)間),則表示加鎖成功
如果set失敗,則每次sleep(x)毫秒后不斷嘗試,直到成功或者超時(shí)
3. 釋放過程
判斷加鎖是否成功
如果成功,則執(zhí)行redis的del刪除
4. 問題思考
加鎖時(shí),鎖的redis key過期時(shí)間多長(zhǎng)合適?
需要根據(jù)業(yè)務(wù)執(zhí)行的時(shí)間長(zhǎng)度來評(píng)估,默認(rèn)30秒滿足絕大部分需求,支持動(dòng)態(tài)修改
加鎖時(shí),重試超時(shí)時(shí)間多長(zhǎng)合適?本文設(shè)置的是過期時(shí)間的1.2倍,目的是在最壞的情況下等待鎖過期后,盡量保證獲取到鎖,否則拋出超時(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ā)問題
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;
/**
* 鎖的過期時(shí)間(秒),默認(rèn)30秒,防止線程獲取鎖后掛掉無法釋放鎖
*/
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ù)
// 樂觀地認(rèn)為:
// 1、同一毫秒內(nèi),隨機(jī)數(shù)相同的概率極小
// 2、釋放非自己線程鎖的幾率極小(release方法有說明這種情況)
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);
// 過期時(shí)間
this.lockExpire = lockExpire;
// 超時(shí)時(shí)間(毫秒),默認(rèn)為expire的1.2倍
this.tryTimeout = lockExpire * 1200;
}
/**
* 嘗試加鎖
*
* 嘗試加鎖的過程將會(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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大学生怎么理财赚钱?理财赚钱的方法有什么
- 下一篇: 信用卡取现额度是什么意思 取现利息不能小