Redisson分布式锁基础使用
//获取锁对象
RLock lock = redissonClient.getLock("lockTest");
//加锁
lock.lock();
try{
System.out.println("业务代码!");
}finally {
//释放锁
lock.unlock();
}
RedissonLock构造方法
public RedissonBaseLock(CommandAsyncExecutor commandExecutor, String name) {
// 调用父类构造函数,传递命令执行器和锁的名称
super(commandExecutor, name);
// 将传入的命令执行器赋值给当前对象的commandExecutor字段
this.commandExecutor = commandExecutor;
// 从连接管理器获取当前实例的唯一标识符并赋值给id字段,UUID.randomUUID()生成
this.id = commandExecutor.getConnectionManager().getId();
// 从连接管理器的配置中获取锁看门狗超时时间(默认30s),并赋值给internalLockLeaseTime字段
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
this.entryName = this.id + ":" + name;
}
lock()加锁
/*
lock对外提供两个重载方法,一个默认使用redisson的续期,
一个使用自己设置的超时时间,不启用自动续期功能
*/
public void lock(long leaseTime, TimeUnit unit) {
try {
this.lock(leaseTime, unit, false);
} catch (InterruptedException var5) {
throw new IllegalStateException();
}
}
public void lock() {
try {
this.lock(-1L, (TimeUnit)null, false);
} catch (InterruptedException var2) {
throw new IllegalStateException();
}
}
获取锁逻辑
/*
- `leaseTime`:锁的持有时间,如果在此时间内没有释放锁,则锁将自动释放。
- `unit`:`leaseTime`的时间单位。
- `interruptibly`:指示线程在等待锁时是否可以被中断。
*/
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
//获取当前线程的线程id
long threadId = Thread.currentThread().getId();
// 尝试获取锁并返回剩余的超时时间
Long ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
if (ttl != null) {
//调用`subscribe`方法订阅锁的状态变化,这样当前线程可以感知到锁被其他线程释放的事件。
RFuture<RedissonLockEntry> future = this.subscribe(threadId);
//根据`interruptibly`参数,使用不同的同步方法来处理订阅的完成,以支持中断。
if (interruptibly) {
this.commandExecutor.syncSubscriptionInterrupted(future);
} else {
this.commandExecutor.syncSubscription(future);
}
try {
//循环等待
while(true) {
//再次尝试获取锁
ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
//`ttl`为`null`,表示锁已被其他线程释放且当前线程成功获取,此时退出循环
if (ttl == null) {
return;
}
if (ttl >= 0L) {
try {
// 阻塞ttl时间,让出cpu,避免占用资源
// 或者接受到订阅消息唤醒,会继续获取锁,并且根据interruptibly判断是否需要抛出打断异常
((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException var13) {
if (interruptibly) {
throw var13;
}
((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
//如果`ttl`小于0(实际在代码中不可能出现,因为`tryAcquire`在成功获取锁时不会返回负数),则根据`interruptibly`参数决定是否以可中断或不可中断的方式等待`Latch`。
} else if (interruptibly) {
((RedissonLockEntry)future.getNow()).getLatch().acquire();
} else {
((RedissonLockEntry)future.getNow()).getLatch().acquireUninterruptibly();
}
}
} finally {
this.unsubscribe(future, threadId);
}
}
}
/*
- `long waitTime`:等待锁的最长时间。
- `long leaseTime`:锁的持有时间,如果为`-1`,则使用默认的锁持有时间。
- `TimeUnit unit`:`waitTime`和`leaseTime`的时间单位。
- `long threadId`:尝试获取锁的线程的ID,用于在Redis中唯一标识锁。
*/
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
// 调用 tryAcquireAsync 方法异步尝试获取锁,并等待结果
return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
开启锁续命逻辑
根据leaseTime是否等于-1来判断是否开启锁续命逻辑
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RFuture ttlRemainingFuture;
if (leaseTime != -1L) {
//如果不是-1,则以开发者指定的过期时间为准
//异步获取锁
ttlRemainingFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
//如果是-1,则以默认的过期时间30s为准
//异步获取锁
ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
}
// 通过`onComplete`方法为`ttlRemainingFuture`添加一个监听器。当异步操作完成时,这个监听器会被调用。
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
// 如果操作成功(`e == null`),并且返回的TTL剩余时间为`null`(表示成功获取了锁),则根据`leaseTime`的值更新`internalLockLeaseTime`或调用`scheduleExpirationRenewal`方法来安排锁的续期。
if (e == null) {
if (ttlRemaining == null) {
if (leaseTime != -1L) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
this.scheduleExpirationRenewal(threadId);
}
}
}
});
return ttlRemainingFuture;
}
异步获取锁的lua脚本调用
Redisson底层是使用Redis的Hash数据结构进行加锁的,key为开发者自定义的名称,filed为线程id,value为重入次数。
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
// 使用Redis的EVAL命令执行一段Lua脚本,尝试获取锁
return commandExecutor.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
//如果锁不存在
// 使用HSET命令将锁信息存储到Redis哈希表中
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
// 设置锁的过期时间(默认30毫秒)
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
// 返回nil表示成功获取锁,对应java的null
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
// 如果锁已存在
// 表示当前线程已经持有锁,可重入锁,增加锁的计数
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
// 设置锁的过期时间(毫秒)
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
// 返回nil表示成功获取锁
"return nil; " +
"end; " +
// 如果锁已被其他线程持有,返回锁的剩余过期时间
"return redis.call('pttl', KEYS[1]);",
// getRawName()对应KEYS[1]、unit.toMillis(leaseTime)对应ARGV[1]、this.getLockName(threadId)对应ARGV[2]
Collections.singletonList(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)})
}
锁续期(看门狗机制)
protected void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
ExpirationEntry oldEntry = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
//判断是否已经存在续约任务
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
this.renewExpiration();
}
}
private void renewExpiration() {
ExpirationEntry ee = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
//判断该锁是否存在续约任务
if (ee != null) {
// 创建一个新的定时任务
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
//再次判断是否存在续约任务
ExpirationEntry ent = (ExpirationEntry)RedissonBaseLock.EXPIRATION_RENEWAL_MAP.get(RedissonBaseLock.this.getEntryName());
if (ent != null) {
Long threadId = ent.getFirstThreadId();
if (threadId != null) {
//通过lua脚本给锁续期
RFuture<Boolean> future = RedissonBaseLock.this.renewExpirationAsync(threadId);
//如果续期失败则删除续约任务
future.onComplete((res, e) -> {
if (e != null) {
RedissonBaseLock.log.error("Can't update lock " + RedissonBaseLock.this.getRawName() + " expiration", e);
RedissonBaseLock.EXPIRATION_RENEWAL_MAP.remove(RedissonBaseLock.this.getEntryName());
} else {
if (res) {
RedissonBaseLock.this.renewExpiration();
}
}
});
}
}
}
//每过 30s / 3 == 10s 续约一次
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
//设置定时任务
ee.setTimeout(task);
}
}
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('pexpire', KEYS[1], ARGV[1]);" +
"return 1; " +
"end;" +
"return 0;",
Collections.singletonList(this.getRawName()), this.internalLockLeaseTime, this.getLockName(threadId));
}
解锁
public void unlock() {
try {
//异步解锁,并等待获取结果
this.get(this.unlockAsync(Thread.currentThread().getId()));
} catch (RedisException var2) {
if (var2.getCause() instanceof IllegalMonitorStateException) {
throw (IllegalMonitorStateException)var2.getCause();
} else {
throw var2;
}
}
}
public RFuture<Void> unlockAsync(long threadId) {
RPromise<Void> result = new RedissonPromise();
//异步解锁
RFuture<Boolean> future = this.unlockInnerAsync(threadId);
future.onComplete((opStatus, e) -> {
//不管是否解锁成功,都会取消锁的续期
this.cancelExpirationRenewal(threadId);
if (e != null) {
result.tryFailure(e);
} else if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
result.tryFailure(cause);
} else {
result.trySuccess((Object)null);
}
});
return result;
}
//如果锁被成功解锁(即计数器减到0),Lua脚本返回`1`;如果锁存在但计数器未减到0,返回`0`;如果锁不存在,返回`nil`
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
//如果当前锁重入次数减为0,则返回nil
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then "+
" return nil;"+
"end; "+
//否则重入次数减一
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); "+
"if (counter > 0) then "+
//如果重入次数大于零,则续期
" redis.call('pexpire', KEYS[1], ARGV[2]); " +
" return 0; " +
"else "+
//否则删除对应的key
" redis.call('del', KEYS[1])" +;
//通过publish命令在指定的频道上发布一个解锁消息
" redis.call('publish', KEYS[2], ARGV[1])" +;
" return 1" +;
"end "+;
"return nil;",
Arrays.asList(this.getRawName(), this.getChannelName()), new Object[]{LockPubSub.UNLOCK_MESSAGE, this.internalLockLeaseTime, this.getLockName(threadId)});
}
代码小白,各位大佬如果发现有什么解释不对的地方,欢迎讨论!!!