0
0
0

Redisson 分布式锁:普通锁、公平锁与读写锁

2026-10-26
文章摘要
|

一、三种锁的区别

Redisson 提供多种分布式锁,常见的有普通锁、公平锁和读写锁。

锁类型

核心特点

适用场景

普通锁 RLock

独占,不保证获取顺序

订单处理、库存更新

公平锁 FairLock

独占,按等待队列顺序获取

需要按申请顺序执行的业务

读写锁 ReadWriteLock

读共享、写独占

读多写少的共享资源

三种锁都支持可重入,未指定固定 leaseTime 时,都可以使用 Watch Dog 自动续期。

二、普通锁 RLock

1. 基本原理

普通锁是一种非公平、可重入的独占锁。

  • 同一时刻,只允许一个线程持有同一把锁。

  • 无论执行读取还是修改操作,都需要等待锁释放。

  • 不保证线程按照申请锁的顺序获取锁。

  • 同一线程可以重复获取自己持有的锁。

例如线程 A、B、C 同时竞争:

申请顺序:A → B → C

可能的获取顺序:A → C → B

2. Java 使用

RLock lock = redissonClient.getLock("lock:order:1001");

boolean acquired = false;

try {
    // 最多等待3秒,启用看门狗
    acquired = lock.tryLock(3, TimeUnit.SECONDS);

    if (!acquired) {
        throw new RuntimeException("获取锁失败");
    }

    // 执行业务逻辑

} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    throw new RuntimeException("获取锁被中断", e);

} finally {
    if (acquired && lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

普通锁适合大部分需要互斥执行的业务,是最常用的 Redisson 分布式锁。

三、公平锁 FairLock

1. 基本原理

公平锁和普通锁一样,都属于独占锁。

区别在于:

公平锁按照线程进入锁等待队列的顺序获取锁(FIFO,先进先出)。

例如:

线程 A 申请锁
线程 B 申请锁
线程 C 申请锁

等待队列:A → B → C

获取顺序:A → B → C

需要注意,这里的先后顺序是进入 Redis 锁等待队列的顺序,而不是 Java 线程创建顺序。

2. Java 使用

RLock lock = redissonClient.getFairLock("lock:order:1001");

boolean acquired = false;

try {
    acquired = lock.tryLock(3, TimeUnit.SECONDS);

    if (!acquired) {
        throw new RuntimeException("获取公平锁失败");
    }

    // 执行业务逻辑

} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    throw new RuntimeException("获取锁被中断", e);

} finally {
    if (acquired && lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

使用方式与普通锁基本一致,只需要将 getLock() 换成 getFairLock()。

3. 底层原理

公平锁通过 Redis 维护等待线程的顺序,核心涉及:

  • Hash:记录持锁线程和重入次数。

  • List:维护等待队列。

  • Sorted Set:辅助记录等待超时信息。

  • Lua:保证队列和锁操作的原子性。

  • Pub/Sub:通知等待线程重新竞争锁。

公平锁的缺点:

  1. 需要额外维护等待队列,开销通常高于普通锁。

  2. 如果队列中存在失效线程,可能造成额外等待。

  3. 无法保证业务请求严格按照创建时间处理,只保证锁等待队列层面的顺序。

因此,一般业务优先使用普通锁,只有明确要求按锁申请顺序执行时,才考虑公平锁。

四、读写锁 ReadWriteLock

1. 基本原理

读写锁并不是三种锁,而是由两种锁组成:

  • 读锁(ReadLock):共享锁,允许多个线程同时读取。

  • 写锁(WriteLock):独占锁,不允许其他线程同时持有冲突的读锁或写锁。

读写锁有三个核心规则:

读读共享、读写互斥、写写互斥。

线程 A

线程 B

能否同时持有

读锁

读锁

可以

读锁

写锁

不可以

写锁

写锁

不可以

2. 读读共享

两个线程都只需要读取数据,可以同时获取读锁。

线程 A → 获取读锁 → 读取配置
线程 B → 获取读锁 → 读取配置

两个线程可以同时执行

例如:

RReadWriteLock rwLock =
        redissonClient.getReadWriteLock("lock:config");

RLock readLock = rwLock.readLock();

readLock.lock();

try {
    // 查询配置
} finally {
    readLock.unlock();
}

3. 读写互斥

线程 A 正在读取数据,线程 B 想修改数据。

线程 A → 获取读锁 → 查询数据
线程 B → 申请写锁 → 等待

线程 A 释放读锁
          ↓
线程 B 才能获取写锁

反过来也一样:当某个线程持有写锁时,其他线程无法获取冲突的读锁。

4. 写写互斥

两个线程都需要修改同一份数据。

线程 A → 获取写锁 → 修改数据
线程 B → 申请写锁 → 等待

线程 A 释放写锁
          ↓
线程 B 获取写锁

写操作必须独占,避免多个线程同时修改共享资源。

5. Java 使用

RReadWriteLock rwLock =
        redissonClient.getReadWriteLock("lock:config");

// 获取读锁
RLock readLock = rwLock.readLock();

// 获取写锁
RLock writeLock = rwLock.writeLock();

查询操作:

readLock.lock();

try {
    // 查询数据
} finally {
    readLock.unlock();
}

修改操作:

writeLock.lock();

try {
    // 修改数据
} finally {
    writeLock.unlock();
}

实际项目中可以使用带等待时间的 tryLock(),避免长时间阻塞。

五、普通锁与读写锁的区别

普通锁在互斥效果上,可以理解为读写锁中的写锁。

但两者并不完全相同。

对比

普通锁 RLock

读写锁 ReadWriteLock

锁的组成

一把独占锁

读锁 + 写锁

多线程同时读

不允许同时持有同一把锁

允许

读写同时执行

不允许同时持有同一把锁

不允许

多线程同时写

不允许

不允许

是否区分读写操作

不区分

区分

适用场景

一般互斥业务

读多写少的共享资源

例如有100个线程查询同一个门店配置:

使用普通锁:

线程 A 查询 → 释放锁
线程 B 查询 → 释放锁
线程 C 查询 → 释放锁
……

同一时间只能有一个线程持锁。

使用读写锁:

线程 A 查询 ─────┐
线程 B 查询 ─────┼── 可以并发执行
线程 C 查询 ─────┘

线程 D 修改 → 等待读锁释放

因此,读写锁可以减少纯读取操作之间不必要的互斥等待。

六、实际开发注意事项

  1. 普通业务优先使用 RLock,不需要为了公平性而使用 FairLock。

  2. 读写锁适用于读多写少、确实需要跨实例协调的共享资源,并非查询数据库就必须加读锁。

  3. 读锁不能随意升级为写锁,否则可能发生等待甚至死锁。

  4. 读写锁不会自动识别 SQL 类型,必须由开发者保证读取使用读锁、修改使用写锁。

  5. 锁要覆盖数据库事务提交,避免释放锁后事务仍未提交。

  6. 不要混用普通锁和读写锁来协调同一业务资源,应统一锁协议。

  7. 读写锁并不能替代数据库事务或条件更新,重要数据仍需数据库层面的保护。

七、总结

1. 普通锁和公平锁有什么区别?

普通锁属于非公平的独占锁,不保证线程获取锁的顺序。公平锁通过维护等待队列,实现 FIFO 获取顺序,但需要额外维护队列,性能开销通常更高。

2. 读写锁是什么?

读写锁由读锁和写锁组成。读锁是共享锁,允许多个线程同时读取;写锁是独占锁,与其他线程的读锁和写锁互斥。

3. 普通锁和读写锁有什么区别?

普通锁无论读写操作都需要独占锁;读写锁允许多个读线程并发持锁,因此更适合需要协调读多写少的共享资源。

4. 普通锁是否相当于读写锁中的写锁?

从不同线程之间的互斥效果来看,可以这样理解。但普通锁是一套独立的互斥锁机制,而写锁属于读写锁的一部分,必须与同一组读锁协调。


八、核心记忆

  • 普通锁 RLock:独占、非公平、可重入,适合一般互斥业务。

  • 公平锁 FairLock:独占、FIFO,适合需要按锁等待顺序处理的业务。

  • 读写锁 ReadWriteLock:读读共享、读写互斥、写写互斥,适合读多写少且需要协调的共享资源。

记住一句话:普通锁解决互斥问题,公平锁在互斥基础上解决排队顺序问题,读写锁在保证写操作互斥的前提下提高读取并发能力。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论