package com.xxxx.is.xxxx.base.redis;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import redis.clients.jedis.Jedis;
import redis.clients.util.Pool;
/**
* 基于Redis实现的分布式锁.
* Note: 基于同一个redis key实现分布式锁时,请注意lock的配置相同.(超时时间等)
*
* @author Andy
* @date 2017/8/25
*/
@Slf4j
public class RedisLock {
private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
/**
* 基于redis key实现Lock.
*/
private String lockKey;
/**
* 锁超时时间,防止线程在入锁以后,无限的执行等待.
*/
private int expireMills = 5 * 1000;
/**
* 锁等待时间,防止线程饥饿.
*/
private int timeoutMills = 30 * 1000;
// Redis连接池
private Pool<Jedis> pool;
private volatile boolean locked = false;
/**
* 锁的下一次超时时间.
*/
private volatile long lockTimeOut;
private final Object mutex = new Object();
/**
* 初始化锁.
*
* @param pool - jedis客户端.
* @param lockKey - 锁的key
*/
public RedisLock(Pool<Jedis> pool, String lockKey) {
Objects.requireNonNull(pool);
checkArgument(lockKey != null && lockKey.length() > 0, "lockKey must be not empty!");
this.pool = pool;
this.lockKey = lockKey + "_lock";
}
/**
* 初始化锁.
*
* @param pool - jedis客户端.
* @param lockKey - 锁的key
* @param timeoutMills - 获取锁的超时时间
*/
public RedisLock(Pool<Jedis> pool, String lockKey, int timeoutMills) {
this(pool, lockKey);
checkArgument(timeoutMills > 0, "timeoutMills must be greater than zero!");
this.timeoutMills = timeoutMills;
}
/**
* 初始化锁.
*
* @param pool - jedis客户端.
* @param lockKey - 锁的key
* @param timeoutMills - 获取锁的超时时间
* @param expireMills - 锁的过期时间
*/
public RedisLock(Pool<Jedis> pool, String lockKey, int timeoutMills, int expireMills) {
this(pool, lockKey, timeoutMills);
checkArgument(expireMills > 0, "expireMills must be greater than zero!");
this.expireMills = expireMills;
}
/**
* 获取redis中lock的key.
*/
public String getLockKey() {
return lockKey;
}
/**
* 获取锁.
*/
public boolean lock() throws InterruptedException {
int timeout = timeoutMills;
synchronized (mutex) {
try (Jedis jedis = pool.getResource()) {
while (timeout >= 0) {
// 失效的时间戳
long expires = System.currentTimeMillis() + expireMills + 1;
lockTimeOut = expires;
// ===========================
// case1: 直接获取lock成功
// ===========================
long ret = jedis.setnx(lockKey, String.valueOf(expires));
if (ret == 1) {
if (log.isDebugEnabled()) {
log.debug("lock setnx, got locked!");
}
locked = true;
return locked;
}
// ===========================
// case2: 判断超时时间.
// ===========================
// redis中的超时时间
String timeoutInRedis = jedis.get(lockKey);
/**
* 1. key可能已经被别的线程删除了,所以必须判断!=null.
* 2. redis中的锁已经超时了
*/
if (timeoutInRedis != null && Long.parseLong(timeoutInRedis) < System
.currentTimeMillis()) {
// 返回redis中的的过期时间,并设置新的过期时间,key不存在时返回为空.
String oldTimeOut = jedis.getSet(lockKey, String.valueOf(expires));
if (oldTimeOut != null && oldTimeOut.equals(timeoutInRedis)) {
if (log.isDebugEnabled()) {
log.debug("lock timeout, get locked!");
}
locked = true;
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
/**
* 延迟0-100毫秒,可以防止饥饿进程的出现。
即,当同时到达多个进程,只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面又来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.
使用随机的等待时间可以一定程度上保证公平性
*
*/
TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(100));
}
}
}
if (log.isDebugEnabled()) {
log.debug("got failed!");
}
return false;
}
/**
* 解锁. Note: 持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,
* 操作完的时候,锁因为超时已经被别人获得,这时就不必解锁了.
*/
public synchronized void unlock() {
if (locked) {
if (System.currentTimeMillis() < lockTimeOut) {
if (log.isDebugEnabled()) {
log.debug("unlocked!");
}
try (Jedis jedis = pool.getResource()) {
jedis.del(lockKey);
}
} else {
if (log.isDebugEnabled()) {
log.debug("not need unlocked!");
}
}
locked = false;
}
}
}
基于Redis实现分布式锁
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 基于redis分布式并发锁的实现理论原理:https://github.com/huangz1990/redis/...