Redission加锁解锁流程
RLock lock = redissonClient.getLock("myLock"); lock.lock(); try { System.out.println("aaa"); } catch (Exception e) { System.out.println("bbb"); } finally { lock.unlock(); }
public void lock() { try { //参数意义 -1 代表不自动释放锁,null时间单位,false加锁期间线程被中断将抛出异常 lock(-1, null, false); } catch (InterruptedException e) { throw new IllegalStateException(); } }
/** * 加锁 * @param leaseTime 锁的使用时间,超过该时间,锁便自动释放 * @param unit 时间单位 * @param interruptibly 加锁期间是否可以被中断 * @return void * @author liekkas 2021-02-27 15:46 */ private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException { //获取当前线程的Id long threadId = Thread.currentThread().getId(); //加锁,如果ttl为null,则说明ttl(time to live)为空,加锁成功,否则加锁不成功。 Long ttl = tryAcquire(-1, leaseTime, unit, threadId); //加锁成功,直接返回 if (ttl == null) { return; } //加锁失败,订阅该线程,释放锁的时候会发布一个消息,锁没有释放的时候则会等待,直到锁释放的时候会执行下面的while循环,重新竞争锁。此处是用了异步的模式。 RFuture<RedissonLockEntry> future = subscribe(threadId); if (interruptibly) { commandExecutor.syncSubscriptionInterrupted(future); } else { commandExecutor.syncSubscription(future); } try { while (true) { //锁被释放,重新竞争锁 ttl = tryAcquire(-1, leaseTime, unit, threadId); // 竞争获取锁成功,退出循环,不再竞争。 if (ttl == null) { break; } // 竞争获取锁失败,则排队等待所释放,重新竞争锁。 if (ttl >= 0) { try { //利用信号量机制阻塞当前线程ttl时间,之后再重新获取锁,如果当前线程被中断,则抛出 InterruptedException异常 future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { if (interruptibly) { throw e; } future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } } else { if (interruptibly) { future.getNow().getLatch().acquire(); } else { future.getNow().getLatch().acquireUninterruptibly(); } } } } finally { //竞争锁成功后,取消订阅该线程Id事件 unsubscribe(future, threadId); } }
/** * 利用future模式异步返回锁的剩余生存时间 * @param waitTime 等待时间 * @param leaseTime 锁的使用时间,超过改时间,锁便自动释放 * @param unit 时间单位 * @param threadId 当前线程Id * @return 锁的剩余生存时间 * @author liekkas 2021-02-27 15:46 */ private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) { return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId)); }
/** * * @param waitTime 等待时间 * @param leaseTime 锁的使用时间,超过改时间,锁便自动释放 * @param unit 时间单位 * @param threadId 当前线程Id * @return 锁的剩余生存时间 * @author liekkas 2021-02-27 15:46 */ private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) { //如果锁的租用时间不为-1,则直接获取锁,到时自动释放锁。 if (leaseTime != -1) { return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } //如果锁的租用时间为-1,则利用默认的租用时间30s,获取锁 RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(waitTime, commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); ttlRemainingFuture.onComplete((ttlRemaining, e) -> { if (e != null) { return; } // 获取锁成功,启用watchDog,自动为锁延长寿命,保证该锁被其持有者释放。 if (ttlRemaining == null) { scheduleExpirationRenewal(threadId); } }); return ttlRemainingFuture; }
/** * 使用lua脚本加锁 * @param waitTime 等待时间 * @param leaseTime 锁的使用时间,超过改时间,锁便自动释放 * @param unit 时间单位 * @param threadId 当前线程Id * @param command 执行lua脚本命令 * @return 锁的剩余生存时间 * @author liekkas 2021-02-27 15:46 */ <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { internalLockLeaseTime = unit.toMillis(leaseTime); /** * nil相当于java的null,后面描述为了方便就用null代替nil * KEYS[1]:锁的名称,ARGV[1]:锁的过期时间,ARGV[2]:线程Id标识名称 * lua脚本的逻辑: * 首先判断KEYS[1]是否存在, * 如果不存在,则利用hincrby命令把KEYS[1]的ARGV[2]的值加一,并且设置KEYS[1]的 * 的过期时间为ARGV[1],返回null. * 如果KEYS[1]存在,则利用hexists判断KEYS[1]的ARGV[2]是否存在, * 如果存在,则利用hincrby命令把KEYS[1]的ARGV[2]的值加一,并且设置KEYS[1]的 * 的过期时间为ARGV[1],返回KEYS[1]的存活时间。 */ return evalWriteAsync(getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then " + "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "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]); " + "return nil; " + "end; " + "return redis.call('pttl', KEYS[1]);", Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }
@Override public void unlock() { try { get(unlockAsync(Thread.currentThread().getId())); } catch (RedisException e) { if (e.getCause() instanceof IllegalMonitorStateException) { throw (IllegalMonitorStateException) e.getCause(); } else { throw e; } }
public RFuture<Void> unlockAsync(long threadId) { RPromise<Void> result = new RedissonPromise<Void>(); //释放锁 RFuture<Boolean> future = unlockInnerAsync(threadId); future.onComplete((opStatus, e) -> { //取消锁的延续时间 cancelExpirationRenewal(threadId); if (e != null) { result.tryFailure(e); return; } if (opStatus == null) { IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + threadId); result.tryFailure(cause); return; } result.trySuccess(null); }); return result; }
protected RFuture<Boolean> unlockInnerAsync(long threadId) { /** * nil相当于java的null,后面描述为了方便就用null代替nil * KEYS[1]:锁的名称,KEYS[2]:发布订阅消息的管道名称, * ARGV[1]:发布的消息内容,ARGV[2]:锁的过期时间,ARGV[3]:线程Id标识名称 * lua脚本的逻辑: * 首先判断KEYS[1]的ARGV[3]是否存在, * 如果不存在,直接返回null,确保锁只能被持有的释放. * 如果存在,则KEYS[1]的ARGV[3]的值减一, * 如果ARGV[3]仍然大于0,由于是可重入锁,则说明该线程仍然持有该锁, * 重新设置过期时间,返回0结束。 * 如果ARGV[3]小于等于于0,则删除KEYS[1],并发布KEYS[2],ARGV[1]消息,该锁已释放。返回1结束。 * 如果存在,则利用hincrby命令把KEYS[1]的ARGV[2]的值加一,并且设置KEYS[1]的 * 的过期时间为ARGV[1],返回KEYS[1]的存活时间。 */ return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "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 " + "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; " + "end; " + "return nil;", Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)); }
总结