Redisson#lock加锁原理

本文深入剖析了Redisson分布式锁的实现机制,包括加锁、解锁过程以及锁续命策略。通过lua脚本实现加锁和释放,利用Redis的发布订阅机制进行解锁通知,确保锁的正确性和可靠性。同时,文章还介绍了重入锁的处理和线程等待唤醒机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

获取锁

# name为锁的key,把name赋值给RedissonObject中的name属性,后续加锁会用到
RLock lock = redissonClient.getLock(name);

加锁源码分析

lock.lock();
@Override
public void lock() {
	try {
		// 主要流程
		lockInterruptibly();
	} catch (InterruptedException e) {
		Thread.currentThread().interrupt();
	}
}
@Override
public void lockInterruptibly() throws InterruptedException {
	// 主要流程 leaseTime默认为 -1
	lockInterruptibly(-1, null);
}
@Override
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
	// 获取到线程ID
	long threadId = Thread.currentThread().getId();
	// 加锁主要流程,返回key的过期时长
	Long ttl = tryAcquire(leaseTime, unit, threadId);
	// lock acquired
	if (ttl == null) {
		return;
	}

	// 使用了Redis的发布订阅模式来唤醒下文等待的线程,在释放锁的时候会发布一条消息,通过释放锁来实现闭环
	RFuture<RedissonLockEntry> future = subscribe(threadId);
	commandExecutor.syncSubscription(future);

	try {
		while (true) {
			// 再次获取锁
			ttl = tryAcquire(leaseTime, unit, threadId);
			// lock acquired
			if (ttl == null) {
				break;
			}

			// waiting for message
			if (ttl >= 0) {
				// 获取信号量,让没有获取到锁的线程睡眠等待,超过上一个锁对象key失效时长以后重新获取锁
				getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
			} else {
				getEntry(threadId).getLatch().acquire();
			}
		}
	} finally {
		unsubscribe(future, threadId);
	}
}
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
	// tryAcquireAsync方法为主要获取锁的方法
	return get(tryAcquireAsync(leaseTime, unit, threadId));
}
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
	// leaseTime默认为 -1
	if (leaseTime != -1) {
		return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
	}
	
	// 默认会调用tryLockInnerAsync方法
	RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
	ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
		if (e != null) {
			return;
		}

		// lock acquired
		if (ttlRemaining == null) {
			// 启用一个子线程来实现锁续命
			scheduleExpirationRenewal(threadId);
		}
	});
	return ttlRemainingFuture;
}
private void scheduleExpirationRenewal(long threadId) {
    if (EXPIRATION_RENEWAL_MAP.containsKey(getEntryName())) {
        return;
    }

    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            // 通过lua脚本实现锁续命
            RFuture<Boolean> future = renewExpirationAsync(threadId);
            future.onComplete((res, e) -> {
                EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                if (e != null) {
                    log.error("Can't update lock " + getName() + " expiration", e);
                    return;
                }
                
                if (res) {
                    // reschedule itself
                    scheduleExpirationRenewal(threadId);
                }
            });
        }
	// 定时调用 默认是internalLockLeaseTime 的三分之一
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

    if (EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), new ExpirationEntry(threadId, task)) != null) {
        task.cancel();
    }
}
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
	return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
			// 判断key是否存在
			"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
				// 重新设置key的过期时长
				"redis.call('pexpire', KEYS[1], ARGV[1]); " +
				"return 1; " +
			"end; " +
			"return 0;",
		Collections.<Object>singletonList(getName()), 
		internalLockLeaseTime, getLockName(threadId));
}
# 主要加锁流程 RedissonLock#tryLockInnerAsync方法
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
	internalLockLeaseTime = unit.toMillis(leaseTime);
	// Redisson是通过lua脚本来实现加锁逻辑的
	return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
			  // 如果key不存在
			  "if (redis.call('exists', KEYS[1]) == 0) then " +
				  // 向redis中设置一个hash类型的值
				  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
				  // 设置过期时间
				  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
				  // 返回null
				  "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]); " +
				  // 返回null
				  "return nil; " +
			  "end; " +
			  "return redis.call('pttl', KEYS[1]);",
				Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

释放锁源码分析

lock.unlock();
@Override
public void unlock() {
	try {
		// 主要流程方法:unlockAsync
		get(unlockAsync(Thread.currentThread().getId()));
	} catch (RedisException e) {
		if (e.getCause() instanceof IllegalMonitorStateException) {
			throw (IllegalMonitorStateException) e.getCause();
		} else {
			throw e;
		}
	}
}
@Override
public RFuture<Void> unlockAsync(long threadId) {
	RPromise<Void> result = new RedissonPromise<Void>();
	// 主要流程
	RFuture<Boolean> future = unlockInnerAsync(threadId);

	future.onComplete((opStatus, e) -> {
		if (e != null) {
			cancelExpirationRenewal(threadId);
			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;
		}
		if (opStatus) {
			cancelExpirationRenewal(null);
		}
		result.trySuccess(null);
	});

	return result;
}
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
	return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
			// 如果key存在
			"if (redis.call('exists', KEYS[1]) == 0) then " +
				// 发布解锁消息
				"redis.call('publish', KEYS[2], ARGV[1]); " +
				"return 1; " +
			"end;" +
			// 释放锁
			"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.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

}

发布完消息会执行LockPubSub#onMessage方法

@Override
protected void onMessage(RedissonLockEntry value, Long message) {
	// 监听解锁逻辑
	if (message.equals(UNLOCK_MESSAGE)) {
		Runnable runnableToExecute = value.getListeners().poll();
		if (runnableToExecute != null) {
			runnableToExecute.run();
		}

		// 主要闭环 获取信号量唤醒等待的线程
		value.getLatch().release();
	} else if (message.equals(READ_UNLOCK_MESSAGE)) {
		while (true) {
			Runnable runnableToExecute = value.getListeners().poll();
			if (runnableToExecute == null) {
				break;
			}
			runnableToExecute.run();
		}
		
		value.getLatch().release(value.getLatch().getQueueLength());
	}
}
### Redisson 分布式锁 实现原理 Redisson 是一种用于 JavaRedis 客户端工具包,它提供了多种分布式对象和服务的功能。其中最常用的是其封装良好的分布式锁功能。 #### 1. **Redisson 锁的核心概念** Redisson 提供的分布式锁主要依赖于 Redis 来实现,并且解决了传统手动实现 Redis 分布式锁时容易遇到的各种问题,比如死锁、误删以及可重入等问题[^1]。这些锁的设计遵循了 JUC 中 `java.util.concurrent.locks.Lock` 接口的标准定义,使得开发者可以直接将其作为标准锁使用。 #### 2. **Redisson 分布式锁的工作流程** Redisson 使用 Redis 数据库中的键值对来表示锁的状态。以下是其实现的主要过程: - 当客户端尝试获取锁时,Redisson 将会在 Redis 上创建一个唯一的 key-value 对应关系。这个 value 值通常是一个 UUID 和线程 ID 的组合字符串。 - 如果该 key 已经存在,则说明已经有其他客户端持有此锁,当前请求会被阻塞直到超时或者成功获得锁为止。 - 成功加锁之后,Redisson 还会启动一个定时任务(称为 WatchDog),定期对该 lock-key 自动续约以防止因网络分区等原因导致提前释放锁的情况发生[^3]。 #### 3. **具体技术细节分析** ##### (1)**公平性和可重入性支持** - **公平性**: 默认情况下,Redisson 不提供严格的 FIFO 公平队列模式,但它可以通过配置参数调整行为逻辑满足特定需求下的近似效果。 - **可重入特性**: 同一线程多次申请同一把锁不会造成死锁现象,每次都会记录递增计数器数值;解锁时也需要按照相同次数逐步减少直至完全释放[^1]。 ##### (2)**过期保护机制 (Lease Time & Auto-Renewal)** 为了避免长时间占用资源引发潜在风险,每把锁都有预设的有效期限(lease time),超过规定时限未完成业务处理则强制回收使用权。与此同时,为了应对突发状况如程序崩溃无法主动解除绑定状态,引入了自动续租策略——即所谓的 “Watch Dog Mechanism”。每当检测到剩余存活周期低于阈值水平时便会触发重新设置到期时间的操作,从而有效规避意外丢失控制权的风险。 ##### (3)**异常恢复能力** 即使在极端条件下发生了诸如服务器重启之类的事件影响到了原有锁定关系,Redisson 能够依据存储于内部元数据信息快速重建上下文环境并恢复正常运作秩序。例如通过监听订阅频道消息感知实例变更通知进而采取相应措施保障整体架构稳健可靠运行[^2]。 #### 4. **代码示例** 下面展示了一个简单的例子演示如何使用 Redisson 创建和管理分布式的互斥锁: ```java import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; public class DistributedLockExample { public static void main(String[] args) throws InterruptedException { // 初始化 Redisson 客户端 Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("anyLock"); try { boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS); // 尝试获取锁,等待最多10秒,保持时间为30秒 System.out.println("Is locked? " + isLocked); if(isLocked){ // 执行受保护的关键区域操作... } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); // 确保最后总是要释放锁 } redisson.shutdown(); } } ``` #### 总结 综上所述,Redisson 的分布式锁实现了高度抽象化的 API 层面设计,极大简化了实际项目开发过程中对于复杂场景下并发访问协调工作的难度。同时凭借内置完善的错误容忍度方案及高效性能表现赢得了广大用户的青睐。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值