2.【Redisson源码】可重入锁互斥流程

【本篇文章基于redisson-3.17.6版本源码进行分析】

通过前面对Redisson可重入锁的LUA脚本分析,我们知道,当执行LUA脚本返回nil的时候,对应java就是返回null,表示当前线程获取分布式锁成功,主要有三种情况:

  • 1、当前锁对应的key在redis不存在,直接加锁,返回nil,加锁成功;
  • 2、当前锁对应的key存在,并且hash key就是当前线程自己,允许重入,返回nil,加锁成功;
  • 3、当前锁对应的key存在,但是hash key是其它线程,获取锁失败,此时返回的是锁剩余过期时间;

Redisson可重入锁实现互斥的关键,就是上述的第三种情况,获取锁失败后,返回锁剩余的过期时间。对应的LUA脚本如下图框起来的部分:

也就是tryLockInnerAsync()加锁方法返回的是锁剩余过期时间,tryAcquire()也是返回的锁剩余过期时间。

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    // 当前线程ID
    long threadId = Thread.currentThread().getId();
    // tryAcquire()尝试获取锁
    Long ttl = tryAcquire(-1, leaseTime, unit, threadId);

    // lock acquired
    // ttl为空表示获取锁成功,则直接返回
    if (ttl == null) {
        return;
    }

    // 未能获取到锁的其它线程
    // 争抢同一把锁的其它线程,订阅channel消息
    // 发布-订阅消息:当释放锁的时候,会通过publish发布一条消息,通知其它等待这个锁的线程,我已经释放锁了,你们可以过来获取了。
    CompletableFuture<RedissonLockEntry> future = subscribe(threadId);
    pubSub.timeout(future);
    RedissonLockEntry entry;
    if (interruptibly) {
        entry = commandExecutor.getInterrupted(future);
    } else {
        entry = commandExecutor.get(future);
    }

    try {
        // 加锁失败的话,循环重试
        while (true) {
            ttl = tryAcquire(-1, leaseTime, unit, threadId);
            // lock acquired
            if (ttl == null) {
                break;
            }

            // waiting for message
            if (ttl >= 0) {
                try {
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    if (interruptibly) {
                        throw e;
                    }
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                }
            } else {
                if (interruptibly) {
                    entry.getLatch().acquire();
                } else {
                    entry.getLatch().acquireUninterruptibly();
                }
            }
        }
    } finally {
        unsubscribe(entry, threadId);
    }
//        get(lockAsync(leaseTime, unit));
}

如上代码,可以看到,lock()方法加锁成功后,会返回nil,此处会判断为 null,直接返回。

当获取锁失败之后,首先会通过subscribe()订阅【"redisson_lock__channel" + 锁key】的消息,也就是订阅锁释放的消息,当前持有锁的线程在释放锁之后,会通过publish指令发布一条消息,这样之前获取锁失败的那些线程就知道锁已经被释放了,可以获取了。

除了订阅消息之外,最关键的还是通过while(true)自旋,不断调用tryAcquire()方法尝试去获取锁,直到加锁成功,才break。如下图:

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/128238899