Redisson 公平锁和读写锁特性、用法、原理及应用场景

211 阅读5分钟

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. 检查是否有写锁持有:若有写锁且非当前线程持有,则阻塞等待;
    2. 无写锁或写锁为当前线程持有(可重入):当前线程的读锁计数 +1,成功获取读锁。
  • 写锁获取逻辑

    1. 检查是否有读锁或写锁持有:
      • 若有其他线程的读锁或写锁,则阻塞等待;
      • 若为当前线程的读锁或写锁(可重入):写锁计数 +1,成功获取写锁。
  • 避免写饥饿:当有写锁等待时,新的读锁请求会被阻塞(需等待写锁完成),防止读锁长期占用导致写锁一直等待。

  • 自动续期:读锁和写锁均支持看门狗机制(默认 30s 续期),避免锁因超时而提前释放。

4. 优缺点与应用场景

  • 优点:读操作并发无阻塞,大幅提升读多写少场景的性能;支持可重入和自动续期。
  • 缺点:写操作仍需排他,写多场景下优势不明显;逻辑较复杂,实现成本高。

应用场景

  • 缓存更新(如大量读请求 + 少量更新操作);
  • 数据查询与修改(如商品详情查询多,库存修改少);
  • 日志系统(大量读日志,少量写日志)。

三、总结:公平锁 vs 读写锁

维度公平锁(RFairLock)读写锁(RReadWriteLock)
核心目标保证锁获取的顺序性(先到先得)区分读写操作,提高读并发效率
互斥规则所有线程竞争按顺序获取读-读不互斥,读-写/写-写互斥
性能略低(需维护等待队列)读多写少场景下性能高
适用场景顺序敏感场景(如任务调度)读多写少场景(如缓存、查询)

选择时需根据业务特点决策:若需严格顺序性,选公平锁;若读操作远多于写操作,选读写锁。