Redisson 提供了多种分布式锁实现,其中 公平锁(RFairLock) 和 读写锁(RReadWriteLock) 是针对特定场景设计的锁机制。它们在解决分布式并发问题时,分别侧重「顺序公平性」和「读写分离效率」,下面详细介绍两者的特性、用法、原理及应用场景。
一、公平锁(RFairLock)
1. 概念与核心特性
公平锁的核心是 「先到先得」:多个线程竞争锁时,锁的分配严格按照线程请求的顺序执行,先发起请求的线程优先获得锁,避免「线程饥饿」(某个线程长期得不到锁)。
与「非公平锁」(默认的可重入锁 RLock)相比,公平锁牺牲了部分性能,换取了锁获取的顺序性。
2. 使用方法
Redisson 中通过 RedissonClient.getFairLock(String lockKey) 获取公平锁实例,用法与普通可重入锁类似,支持可重入性和自动续期(看门狗机制)。
示例代码:
// 获取公平锁实例
RFairLock fairLock = redissonClient.getFairLock("fair_lock_key");
try {
// 加锁(可设置超时时间,默认启用看门狗续期)
fairLock.lock(30, TimeUnit.SECONDS);
// 业务逻辑(如顺序敏感的任务处理)
doBusiness();
} finally {
// 释放锁
if (fairLock.isHeldByCurrentThread()) {
fairLock.unlock();
}
}
3. 实现原理
Redisson 公平锁的底层依赖 Redis 的 List 结构 和 发布订阅机制 实现顺序性:
- 等待队列:所有竞争锁的线程会先将自己的请求信息(如线程标识)放入一个 Redis List 中,形成「等待队列」,保证请求顺序。
- 锁竞争逻辑:
- 线程尝试获取锁时,先检查等待队列中是否有比自己更早的请求:
- 若没有,直接尝试获取锁;
- 若有,则进入队列等待。
- 当持有锁的线程释放锁时,会从等待队列中取出最早的请求线程,通过发布订阅机制通知其获取锁。
- 线程尝试获取锁时,先检查等待队列中是否有比自己更早的请求:
- 原子性保证:通过 Lua 脚本确保「检查队列 + 获取锁 + 入队/出队」的操作原子性,避免并发问题。
4. 优缺点与应用场景
- 优点:保证锁获取的顺序性,避免线程饥饿,适合对执行顺序敏感的场景。
- 缺点:需要维护等待队列和顺序检查,性能略低于非公平锁(额外的队列操作开销)。
应用场景:
- 任务调度系统(如定时任务执行,需按提交顺序处理);
- 分布式队列消费(需保证消息按入队顺序处理);
- 任何需要严格按请求顺序执行的业务(如秒杀中的订单创建顺序)。
二、读写锁(RReadWriteLock)
1. 概念与核心特性
读写锁(Read-Write Lock)是一种「读写分离」的锁机制,核心是 区分读操作和写操作的并发控制,规则如下:
- 读锁(RReadLock):允许多个线程同时持有,适合共享资源的读取(无修改);
- 写锁(RWriteLock):是排他锁,仅允许一个线程持有,适合资源的修改;
- 互斥规则:
- 读锁与读锁:不互斥(可同时持有);
- 读锁与写锁:互斥(读锁持有期间写锁需等待,反之亦然);
- 写锁与写锁:互斥(仅一个写线程可持有)。
读写锁的设计目标是 提高读多写少场景的并发效率(避免读操作之间的互斥阻塞)。
2. 使用方法
Redisson 中通过 RedissonClient.getReadWriteLock(String lockKey) 获取读写锁实例,再分别获取读锁(readLock())和写锁(writeLock())。
示例代码:
// 获取读写锁实例
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rw_lock_key");
// 读操作:获取读锁
RReadLock readLock = rwLock.readLock();
// 写操作:获取写锁
RWriteLock writeLock = rwLock.writeLock();
// 读操作示例
try {
readLock.lock(); // 读锁可被多个线程同时获取
// 执行读逻辑(如查询数据)
queryData();
} finally {
if (readLock.isHeldByCurrentThread()) {
readLock.unlock();
}
}
// 写操作示例
try {
writeLock.lock(); // 写锁排他,仅当前线程持有
// 执行写逻辑(如更新数据)
updateData();
} finally {
if (writeLock.isHeldByCurrentThread()) {
writeLock.unlock();
}
}
3. 实现原理
Redisson 读写锁的底层通过 Redis 的 Hash 结构 存储锁状态,并通过 Lua 脚本保证操作原子性,核心逻辑如下:
-
锁状态存储:使用 Redis Hash 结构记录锁的持有信息,Key 为锁名称,Hash 字段包括:
write_lock:记录当前持有写锁的线程 ID 和重入次数(如{threadId: 1, count: 2});read_locks:记录所有持有读锁的线程 ID 及各自的重入次数(如{threadId1: 3, threadId2: 1})。
-
读锁获取逻辑:
- 检查是否有写锁持有:若有写锁且非当前线程持有,则阻塞等待;
- 无写锁或写锁为当前线程持有(可重入):当前线程的读锁计数 +1,成功获取读锁。
-
写锁获取逻辑:
- 检查是否有读锁或写锁持有:
- 若有其他线程的读锁或写锁,则阻塞等待;
- 若为当前线程的读锁或写锁(可重入):写锁计数 +1,成功获取写锁。
- 检查是否有读锁或写锁持有:
-
避免写饥饿:当有写锁等待时,新的读锁请求会被阻塞(需等待写锁完成),防止读锁长期占用导致写锁一直等待。
-
自动续期:读锁和写锁均支持看门狗机制(默认 30s 续期),避免锁因超时而提前释放。
4. 优缺点与应用场景
- 优点:读操作并发无阻塞,大幅提升读多写少场景的性能;支持可重入和自动续期。
- 缺点:写操作仍需排他,写多场景下优势不明显;逻辑较复杂,实现成本高。
应用场景:
- 缓存更新(如大量读请求 + 少量更新操作);
- 数据查询与修改(如商品详情查询多,库存修改少);
- 日志系统(大量读日志,少量写日志)。
三、总结:公平锁 vs 读写锁
| 维度 | 公平锁(RFairLock) | 读写锁(RReadWriteLock) |
|---|---|---|
| 核心目标 | 保证锁获取的顺序性(先到先得) | 区分读写操作,提高读并发效率 |
| 互斥规则 | 所有线程竞争按顺序获取 | 读-读不互斥,读-写/写-写互斥 |
| 性能 | 略低(需维护等待队列) | 读多写少场景下性能高 |
| 适用场景 | 顺序敏感场景(如任务调度) | 读多写少场景(如缓存、查询) |
选择时需根据业务特点决策:若需严格顺序性,选公平锁;若读操作远多于写操作,选读写锁。