public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
private String lockKey;
/**
* 锁超时时间,防止线程在入锁以后,无限的执行等待
*/
private int expireMsecs = 60 * 1000;
/**
* 锁等待时间,防止线程饥饿
*/
private int timeoutMsecs = 20 * 1000;
public volatile boolean locked = false;
/**
* Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
*
* @param lockKey lock key (ex. account:1, ...)
*/
public RedisLock(String lockKey) {
this.lockKey = "lock_"+lockKey ;
}
/**
*/
public RedisLock( String lockKey, int timeoutMsecs) {
this(lockKey);
this.timeoutMsecs = timeoutMsecs;
}
/**
*
*/
public RedisLock( String lockKey, int timeoutMsecs, int expireMsecs) {
this( lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
}
/**
*/
public String getLockKey() {
return lockKey;
}
private String get(final String key) {
String res = null;
Jedis jedis=null;
try {
jedis=JedisUtil.getJedis();
jedis.select(10);
res=jedis.get(key);
} catch (Exception e) {
logger.error("get redis error, key : {}", key);
}finally {
if(jedis!=null) {
jedis.close();
}
}
return res;
}
/**
* 尝试获取锁
* @return 是否获取成功
*/
public boolean tryGetLock() {
String res = null;
Jedis jedis=null;
try {
jedis=JedisUtil.getJedis();
jedis.select(10);
Date now=new Date();
Thread thread=Thread.currentThread();
res = jedis.set(lockKey, now.getTime()+"_"+thread.getId()+"_"+thread.hashCode()+"_"+hashCode(), SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireMsecs);
if (LOCK_SUCCESS.equals(res)) {
locked=true;
jedis.hset("lock_"+DateFormatUtil.format(now, "yyyy-MM-dd_HH_mm"),lockKey,"");
return true;
}
} catch (Exception e) {
logger.error("getset redis error, key : {}", lockKey);
}finally {
if(jedis!=null) {
jedis.close();
}
}
return false;
}
/**
*/
public synchronized void unlock() {
if (locked) {
Jedis jedis=null;
try {
jedis=JedisUtil.getJedis();
jedis.select(10);
String res=jedis.get(lockKey);
if(StringUtils.isNotBlank(res)){
// 解锁的时候 取值对比 hashCode 判断是不是 当前锁对象加的锁,如果不是 则不执行任何操作
int lockObjHashCode=Integer.parseInt(res.split("_")[3]);
if(lockObjHashCode!=hashCode()) {
return;
}
jedis.del(lockKey);
Date keyDate=new Date(Long.parseLong(res.split("_")[0]));
jedis.hdel("lock_"+DateFormatUtil.format(keyDate, "yyyy-MM-dd_HH_mm"),lockKey);
}
locked = false;
} catch (Exception e) {
logger.error("unlock redis error, lockKey : {}", lockKey);
}finally {
if(jedis!=null) {
jedis.close();
}
}
}
}
/**
* 获得 lock.
* 实现思路: 主要是使用了redis 的setnx命令,缓存了锁.
* reids缓存的key是锁的key,所有的共享, value是锁过期时间+线程ID+线程hashCode+锁对象hashCode
* 执行过程:
* 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
* 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
* 3.锁的内容 添加 线程ID 以及 线程hashCode ,用于处理 同一线程下,多层子逻辑需要对同一个数据重复加锁异常的问题 对比判断如果是同一线程的子操作,则默认已经锁定,无需锁等待以及重复锁
* 4.锁的内容 添加 加锁对象本身hashCode ,为了解锁的时候对比判断是不是 加锁的那个对象执行的解锁操作, 避免多层业务 重复锁, 子操作 解锁导致,锁被非正常解锁
*
*/
public synchronized boolean lock() throws InterruptedException {
int timeout = timeoutMsecs;
while (timeout >= 0) {
if (this.tryGetLock()) {
locked = true;
return true;
}
String currentValueStr = this.get(lockKey); //redis里的时间
Long cacheTime=Long.parseLong(currentValueStr.split("_")[0]);
long threadId=Long.parseLong(currentValueStr.split("_")[1]);
int threadHashCode=Integer.parseInt(currentValueStr.split("_")[2]);
Thread thread=Thread.currentThread();
if(threadHashCode==thread.hashCode()&&threadId==thread.getId()) {
return true;
}
if (currentValueStr != null &&cacheTime+expireMsecs + 1 < System.currentTimeMillis()) {
return false;
}
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
/*
延迟100 毫秒, 这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程,
只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.
使用随机的等待时间可以一定程度上保证公平性
*/
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
return false;
}
}