@Component
public class DistributedLockClient {
@Autowired
private StringRedisTemplate redisTemplate;
private String uuid;
//生成分布式锁唯一id的前半部分
public DistributedLockClient ( ) {
this.uuid = UUID.randomUUID( ) .toString( ) ;
}
public DistributedRedisLock getRedisLock( String lockName) {
return new DistributedRedisLock( redisTemplate, lockName,uuid) ;
}
}
// 实现Lock接口的tylock()和 unlock()方法
public class DistributedRedisLock implements Lock {
private StringRedisTemplate redisTemplate;
private String lockName;
private String uuid;
private long expire = 30 ;
public DistributedRedisLock( StringRedisTemplate redisTemplate, String lockName, String uuid) {
this.lockName = lockName;
this.redisTemplate = redisTemplate;
// 生成分布式锁的唯一id
this.uuid = uuid + ":" + Thread.currentThread( ) .getId( ) ;
}
@Override
public void lock ( ) {
this.tryLock( ) ;
}
@Override
public void lockInterruptibly( ) throws InterruptedException {
}
@Override
public boolean tryLock ( ) {
try {
return this.tryLock( -1L, TimeUnit.SECONDS ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
return false ;
}
/**
* 加锁方法
* @param time 设置锁续期时间
* @param unit
* @return
* @throws InterruptedException
*/
@Override
public boolean tryLock( long time, TimeUnit unit) throws InterruptedException {
if( time != -1) {
this.expire = unit.toSeconds( time) ;
}
// lua脚本判断锁是否存在以及是否是当前用户所持有的锁,对应于两种情况,
// 如果锁不存在则有hincrby生成key,并且value为0
// 如果锁存在那么也是通过hincrby自增value值+1,value值相当于重入获取锁的次数
// 这里的获取锁和设置过期时间是通过lua脚本保证的原子性,过期时间是为了防止服务器宕机导致的死锁问题,因为你一设置好锁,但是此时你宕机了,就没办法释放锁了,所以就会导致死锁,这个锁没办法得到释放
String script = "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
"then " +
"redis.call('hincrby',KEYS[1],ARGV[1],1) " +
"redis.call('expire',KEYS[1],ARGV[2]) " +
"return 1 " +
"else " +
"return 0 " +
"end" ;
// 获取锁失败进入 while 循环
while ( ! this.redisTemplate.execute( new DefaultRedisScript<> ( script, Boolean.class) , Arrays.asList( lockName) , uuid, String.valueOf( expire)) ) {
Thread.sleep( 50 ) ;
}
// 加锁成功或者获取可重入锁成功,返回之前,开启定时器自动续期
this.renewExpire( ) ;
return true ;
}
/**
* 解锁方法
*/
@Override
public void unlock ( ) {
// 判断自己的锁是否存在(hexists),如果不存在则返回nil
// 如果自己的锁存在,则减一(hincrby -1),判断减1后的值是否为0,为0则释放 锁(del lock)并返回1
// 不为0,返回0
String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 0 " +
"then " +
" return nil " +
"elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 " +
"then " +
" return redis.call('del',KEYS[1]) " +
"else " +
" return 0 " +
"end" ;
Long flag = this.redisTemplate.execute( new DefaultRedisScript<> ( script,Long.class) ,Arrays.asList( lockName) ,uuid) ;
if( flag == null) {
throw new IllegalMonitorStateException( "这个锁不属于你当前的锁" ) ;
}
}
@Override
public Condition newCondition ( ) {
return null;
}
private void renewExpire ( ) {
String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
"then " +
" return redis.call('expire',KEYS[1],ARGV[2]) " +
"else " +
" return 0 " +
"end" ;
// 定时器自动续期,每隔1/3*expire就开始自动续期,直到if条件为false之后结束续期,也就是锁被释放了就不需要续期了
new Timer( ) .schedule( new TimerTask ( ) {
@Override
public void run ( ) {
if ( redisTemplate.execute( new DefaultRedisScript<> ( script,Boolean.class) , Arrays.asList( "lockName" ) ,uuid,String.valueOf( expire)) ) {
renewExpire( ) ;
}
}
} , this.expire * 1000 / 3 ) ;
}
}