Redis分布式锁详解二---Redisson源码简单解析

先看一张图

在这里插入图片描述

1、redissonLock.lock()

1.1、lock()

当我们进入到 Redisson 的lock方法时,会走到下面的代码逻辑。
1、尝试去获取锁。
2、获取锁成功的话,走1.2,去构建看门狗什么的。
3、获取锁失败的话,进入自旋,并等待相应的时间去重新获取锁,知道锁获取成功。

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
	// 获取当前线程id
    long threadId = Thread.currentThread().getId();
    // 注意,这里传入了一个 -1L
    Long ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
    // 这里 ttl 不等于 null的时候,说明没有获取到锁
    if (ttl != null) {
        RFuture<RedissonLockEntry> future = this.subscribe(threadId);
        if (interruptibly) {
            this.commandExecutor.syncSubscriptionInterrupted(future);
        } else {
            this.commandExecutor.syncSubscription(future);
        }

        try {
        	// 进行自旋,然后再次尝试获取锁
            while(true) {
                ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
                if (ttl == null) {
                    return;
                }

                if (ttl >= 0L) {
                    try {
                        ((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException var13) {
                        if (interruptibly) {
                            throw var13;
                        }

                        ((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                } else if (interruptibly) {
                    ((RedissonLockEntry)future.getNow()).getLatch().acquire();
                } else {
                    ((RedissonLockEntry)future.getNow()).getLatch().acquireUninterruptibly();
                }
            }
        } finally {
            this.unsubscribe(future, threadId);
        }
    }
}

1.2、tryAcquire

这个方法进来之后执行的是 tryAcquireAsync 方法
1、先去尝试获取锁。
2、获取锁成功的话,就去设置一个看门狗的定时任务,用于给锁续命用。
3、获取锁失败的话,就退出这个方法,走1.1,进入自旋状态重新获取锁。

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture ttlRemainingFuture;
    // 由于前面传入的值为 -1L ,所以这里不走
    if (leaseTime != -1L) {
        ttlRemainingFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {
    	// 进入这个方法获取锁,获取锁成功的话,则会返回null
    	// 获取锁失败的话,会返回当前拥有锁的线程还有多长时间,进入 1.3
        ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    }

	// 在这里主要就是当获取锁成功后,设置一个定时任务
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        if (e == null) {
        	// 剩余时间等于null,说明获取锁成功了
            if (ttlRemaining == null) {
                if (leaseTime != -1L) {
                    this.internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                	// 设置一个定时任务,走1.4
                    this.scheduleExpirationRenewal(threadId);
                }
            }

        }
    });
    return ttlRemainingFuture;
}

1.3、tryLockInnerAsync()

进来之后我们发现这里有一个lua脚本
尝试获取锁,如果获取到了就创建定时任务,指定看门狗对相应的锁续命
如果获取锁失败,就返回拥有锁线程的剩余时间

<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, command, 
    	// 这里面的 KEYS[1] 就是我们在最开始获取的Redis的那把锁,看那个属性或者说是锁是否存在
    	"if (redis.call('exists', KEYS[1]) == 0) " +
        "then " +
        	// 如果不存在,走下面逻辑,给当前线程设置值加1,也就是1
            "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 " +
        	// 给当前线程的数值加1
            "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(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)});
}

1.4、scheduleExpirationRenewal(long threadId)

主要作用就是去调用循环续命的那个方法

protected void scheduleExpirationRenewal(long threadId) {
    RedissonBaseLock.ExpirationEntry entry = new RedissonBaseLock.ExpirationEntry();
    RedissonBaseLock.ExpirationEntry oldEntry = (RedissonBaseLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
    if (oldEntry != null) {
        oldEntry.addThreadId(threadId);
    } else {
        entry.addThreadId(threadId);

        try {
        	// 走到这里,里面会递归调用这个方法续命,否则销毁,走1.5
            this.renewExpiration();
        } finally {
            if (Thread.currentThread().isInterrupted()) {
                this.cancelExpirationRenewal(threadId);
            }

        }
    }

}

1.5、renewExpiration()

这个方法的主要作用就是
1、设置一个定时任务,超过指定时间的 1/3 就会循环调用一次。
2、判断当前线程是否存在,如果存在,根据指定的时间重置过期时间,如果不存在,说明任务已经执行完成,销毁看门狗线程。

private void renewExpiration() {
    RedissonBaseLock.ExpirationEntry ee = (RedissonBaseLock.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 {
                RedissonBaseLock.ExpirationEntry ent = (RedissonBaseLock.ExpirationEntry)RedissonBaseLock.EXPIRATION_RENEWAL_MAP.get(RedissonBaseLock.this.getEntryName());
                if (ent != null) {
                    Long threadId = ent.getFirstThreadId();
                    // 给线程续命,里面调用set方法充重置线程时间
                    if (threadId != null) {
                        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();
                                } else {
                                	// 如果线程不存在,就关闭销毁线程
                                    RedissonBaseLock.this.cancelExpirationRenewal((Long)null);
                                }

                            }
                        });
                    }
                }
            }
        }, 
        // 每次过设置时间的 1/3 时,就会再次执行定时任务
		this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
        ee.setTimeout(task);
    }
}

2、redissonLock.unlock()

2.1、unlock()

主方法,调用 unlockAsync() 方法,走2.2

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;
        }
    }
}

2.2、unlockAsync(long threadId)

1、修改状态,释放锁等,走2.3
2、删除线程,释放一些其它资源

public RFuture<Void> unlockAsync(long threadId) {
    RPromise<Void> result = new RedissonPromise();
    // 解锁的主要操作,走2.3
    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;
}

2.3、unlockInnerAsync()

1、主要对锁的状态进行修改,lock一次就加1,unlock一次就减1。
2、当减到0的时候就删除这个数据,就是释放锁,并广播其它线程可以加锁了。

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
    // 判断当前这个锁是否存在
    "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) " +
    "then " +
    	// 如果等于0,说明锁已经不存在了,返回null
        "return nil;" +
    "end; " +
    // 定义一个本地变量对锁的状态减去1,并存储在counter中
    "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
    // 如果counter大于0
    "if (counter > 0) " +
    "then " +
    	// 设置一个过期时间并返回0
        "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(this.getRawName(), this.getChannelName()), new Object[]{LockPubSub.UNLOCK_MESSAGE, this.internalLockLeaseTime, this.getLockName(threadId)});
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值