Redission的tryLock源码分析

文章详细分析了Redisson锁的可重试机制,通过tryLock方法展示了在等待时间内如何不断尝试获取锁以及在未获取到锁时如何订阅其他线程释放锁的信号。同时,介绍了看门狗机制,当未设置锁超时时,看门狗会每10秒更新锁的有效期,确保即使在宕机情况下也能保证锁的安全释放。
摘要由CSDN通过智能技术生成

1、可重试机制源码分析 

//    tryLock方法
 /**
     * 
     * @param waitTime 当前线程在waitTime内未获取到锁,则会不断重试,而不是直接返回true或者false
     * @param leaseTime 锁超时释放时间
     * @param unit 时间单位
     * @return
     * @throws InterruptedException
     */
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        //距离锁释放的剩余时间(tryAcquire返回Null说明获取到锁,未返回null而是返回当前锁距离释放的剩余时间则表示未获取到锁)
        Long ttl = this.tryAcquire(leaseTime, unit, threadId);
        /**
         * 获取到锁
         */
        if (ttl == null) {
            return true;
        } else {
            /**
             * 没获取到锁
             */
            time -= System.currentTimeMillis() - current;
            if (time <= 0L) {
                //没获取到锁,且当前需要等待的时间为0了,直接返回false
                this.acquireFailed(threadId);
                return false;
            } else {
                //还有剩余等待时间需要去等待
                current = System.currentTimeMillis();
                //不需要立即尝试(以免浪费资源),则其订阅其他线程释放锁的信号。因其他线程释放锁后,会publish一个信号
                RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
                //由于收到信号的时间不确定,所以执行等待(await)。若等待time结束还未收到通知,则进入if快,且取消订阅
                if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
                    if (!subscribeFuture.cancel(false)) {
                        subscribeFuture.onComplete((res, e) -> {
                            if (e == null) {
                                this.unsubscribe(subscribeFuture, threadId);
                            }

                        });
                    }

                    this.acquireFailed(threadId);
                    return false;
                } else {
                    //在waitTime时间内收到了锁被释放的通知
                    try {
                        time -= System.currentTimeMillis() - current;
                        if (time <= 0L) {
                            this.acquireFailed(threadId);
                            boolean var20 = false;
                            return var20;
                        } else {
                            boolean var16;
                            //用循环模拟不断重试的过程(下面才是重试代码,同上面的逻辑)
                            do {
                                long currentTime = System.currentTimeMillis();
                                ttl = this.tryAcquire(leaseTime, unit, threadId);
                                if (ttl == null) {
                                    var16 = true;
                                    return var16;
                                }

                                time -= System.currentTimeMillis() - currentTime;
                                if (time <= 0L) {
                                    this.acquireFailed(threadId);
                                    var16 = false;
                                    return var16;
                                }

                                currentTime = System.currentTimeMillis();
                                //锁距离被删除剩余的时间<当前线程还需等待时间
                                if (ttl >= 0L && ttl < time) {
                                    //getLatch:返回值为信号量,这里是利用信号量机制尝试等待并获取别人释放的信号,且其有最大等待时间
                                    ((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                                } else {
                                    ((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                                }

                                time -= System.currentTimeMillis() - currentTime;
                            } while(time > 0L);

                            this.acquireFailed(threadId);
                            var16 = false;
                            return var16;
                        }
                    } finally {
                        //取消订阅
                        this.unsubscribe(subscribeFuture, threadId);
                    }
                }
            }
        }
    }

 2、看门狗机制源码分析

 //看门狗机制的实现
    private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
        //自己设置了锁超时时间,则不用看门狗机制
        if (leaseTime != -1L) {
            return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            //未设置锁超时时间,则默认超时时间为30s,且利用看门狗机制开启定时任务,每隔10s更新有效器至最初值
            RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
            //未来任务的onComplete函数:因为上面的方法为异步调用,所以此时的方法相当于等待异步调用返回后才进行执行
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                //没抛异常且获取锁成功,则执行下面业务逻辑
                if (e == null) {
                    if (ttlRemaining == null) {
                        //主要是利用该方法进行时间的更新
                        this.scheduleExpirationRenewal(threadId);
                    }

                }
            });
            return ttlRemainingFuture;
        }
    }

其中的 scheduleExpirationRenewal()方式来更新锁超时时间

 private void renewExpiration() {
        //ExpirationEntry为一个类,该类中有一个map和一个timeOut定时任务
        RedissonLock.ExpirationEntry ee = (RedissonLock.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 {
                    RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
                    if (ent != null) {
                        Long threadId = ent.getFirstThreadId();
                        if (threadId != null) {
                            //底层也为Lua脚本,用于更新当前key的过期时间为30s,以此实现超时续约
                            RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
                            future.onComplete((res, e) -> {
                                if (e != null) {
                                    RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
                                } else {
                                    if (res) {
                                        RedissonLock.this.renewExpiration();
                                    }

                                }
                            });
                        }
                    }
                }
            }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
            //每隔this.internalLockLeaseTime / 3L则执行一次该定时任务。
            //所以,只要将ExpirationEntry ee装入concurrentHashMap,则其定时任务每隔10s就会一直执行
            ee.setTimeout(task);
        }
    }

3、对于看门狗机制,献上自己的一点理解

1)在没有设置超时时间的情况下,才会开启超时续约。若设置了锁超时时 间,则在锁超时后会被释放 

2)为什么在没有设置锁超时时间的情况下,要设置看门狗机制不断更新锁 超时时间?

        首先要明白,redission的锁机制底层就是使用的redis的setnx。若没有设置锁超时时间的话,也就是redis中的key没有设置超时时间,那该key只有用户显示del时,该key才会被删除,即该锁被释放。如果系统在未释放锁宕机了,其永远不会del该key,则该锁一直得不到释放,就会出问题。所以在设置锁的key时,系统默认给其一个过期时间,但用户是不想让他过期的,那redission的办法就是,让锁要被释放时,就更新他的有效期,使其不会释放,在用户看来该锁就是不会过期的(实际上是一直在更新有效期)。但遇到宕机时,其ttl不会再被更新,那么该锁就会超时释放,而不会等待显示调用del才释放,保证了安全性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值