packagecc.lixiaohui.lock;
importjava.util.concurrent.TimeUnit;
importredis.clients.jedis.Jedis;
/**
*
* 基于Redis的SETNX操作实现的分布式锁
*
* 获取锁时最好用lock(long time, TimeUnit unit), 以免网路问题而导致线程一直阻塞
*
* SETNC操作参考资料
*
*
* @author lixiaohui
*
*/
publicclassRedisBasedDistributedLockextendsAbstractLock {
privateJedis jedis;
// 锁的名字
protectedString lockKey;
// 锁的有效时长(毫秒)
protectedlonglockExpires;
publicRedisBasedDistributedLock(Jedis jedis, String lockKey,longlockExpires) {
this.jedis = jedis;
this.lockKey = lockKey;
this.lockExpires = lockExpires;
}
// 阻塞式获取锁的实现
protectedbooleanlock(booleanuseTimeout,longtime, TimeUnit unit,booleaninterrupt)throwsInterruptedException{
if(interrupt) {
checkInterruption();
}
longstart = System.currentTimeMillis();
longtimeout = unit.toMillis(time);// if !useTimeout, then it's useless
while(useTimeout ? isTimeout(start, timeout) :true) {
if(interrupt) {
checkInterruption();
}
longlockExpireTime = System.currentTimeMillis() + lockExpires +1;//锁超时时间
String stringOfLockExpireTime = String.valueOf(lockExpireTime);
if(jedis.setnx(lockKey, stringOfLockExpireTime) ==1) {// 获取到锁
// TODO 成功获取到锁, 设置相关标识
locked = true;
setExclusiveOwnerThread(Thread.currentThread());
returntrue;
}
String value = jedis.get(lockKey);
if(value !=null&& isTimeExpired(value)) {// lock is expired
// 假设多个线程(非单jvm)同时走到这里
String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); // getset is atomic
// 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)
// 加入拿到的oldValue依然是expired的,那么就说明拿到锁了
if(oldValue !=null&& isTimeExpired(oldValue)) {
// TODO 成功获取到锁, 设置相关标识
locked = true;
setExclusiveOwnerThread(Thread.currentThread());
returntrue;
}
} else{
// TODO lock is not expired, enter next loop retrying
}
}
returnfalse;
}
publicbooleantryLock() {
longlockExpireTime = System.currentTimeMillis() + lockExpires +1;//锁超时时间
String stringOfLockExpireTime = String.valueOf(lockExpireTime);
if(jedis.setnx(lockKey, stringOfLockExpireTime) ==1) {// 获取到锁
// TODO 成功获取到锁, 设置相关标识
locked = true;
setExclusiveOwnerThread(Thread.currentThread());
returntrue;
}
String value = jedis.get(lockKey);
if(value !=null&& isTimeExpired(value)) {// lock is expired
// 假设多个线程(非单jvm)同时走到这里
String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); // getset is atomic
// 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)
// 假如拿到的oldValue依然是expired的,那么就说明拿到锁了
if(oldValue !=null&& isTimeExpired(oldValue)) {
// TODO 成功获取到锁, 设置相关标识
locked = true;
setExclusiveOwnerThread(Thread.currentThread());
returntrue;
}
} else{
// TODO lock is not expired, enter next loop retrying
}
returnfalse;
}
/**
* Queries if this lock is held by any thread.
*
* @return {@code true} if any thread holds this lock and
* {@code false} otherwise
*/
publicbooleanisLocked() {
if(locked) {
returntrue;
} else{
String value = jedis.get(lockKey);
// TODO 这里其实是有问题的, 想:当get方法返回value后, 假设这个value已经是过期的了,
// 而就在这瞬间, 另一个节点set了value, 这时锁是被别的线程(节点持有), 而接下来的判断
// 是检测不出这种情况的.不过这个问题应该不会导致其它的问题出现, 因为这个方法的目的本来就
// 不是同步控制, 它只是一种锁状态的报告.
return!isTimeExpired(value);
}
}
@Override
protectedvoidunlock0() {
// TODO 判断锁是否过期
String value = jedis.get(lockKey);
if(!isTimeExpired(value)) {
doUnlock();
}
}
privatevoidcheckInterruption()throwsInterruptedException {
if(Thread.currentThread().isInterrupted()) {
thrownewInterruptedException();
}
}
privatebooleanisTimeExpired(String value) {
returnLong.parseLong(value)
}
privatebooleanisTimeout(longstart,longtimeout) {
returnstart + timeout > System.currentTimeMillis();
}
privatevoiddoUnlock() {
jedis.del(lockKey);
}
}