文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

Java 锁相关【三、ReentrantLock 与显式锁机制全解析】

ReentrantLock 与显式锁机制全解析

在 Java 并发编程中,除了 synchronized 提供的内置锁外,java.util.concurrent.locks 包下的 显式锁(Explicit Lock) 机制为开发者提供了更强大、更灵活的控制手段。其中最核心的实现就是 ReentrantLock

本文将从 基本概念 → 内部实现 → 与 synchronized 的比较 → 高级特性 → 工程实践 → 优缺点分析 等角度,对 ReentrantLock 做全面解析。


一、显式锁与内置锁的区别

synchronized隐式锁,编译器和 JVM 自动管理锁的获取与释放;而 ReentrantLock显式锁,需要开发者手动获取与释放。

1.1 使用方式

  • synchronized

    synchronized (lock) {
        // 临界区
    }
    
  • ReentrantLock

    ReentrantLock lock = new ReentrantLock();
    lock.lock();
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
    

1.2 差异对比

特性synchronizedReentrantLock
获取与释放方式自动(编译器生成字节码)手动调用 lock()/unlock()
公平性选择不支持支持公平锁与非公平锁
可中断性不支持lockInterruptibly() 可中断等待
尝试获取锁不支持tryLock() 可立即或超时尝试
条件变量(Condition)通过 wait/notify多个 Condition,更灵活
可重入性支持支持
性能(JDK 1.6+ 后)已优化,性能接近 ReentrantLock高并发竞争下略优

二、ReentrantLock 的基本原理

2.1 可重入性

“可重入”指同一线程可以重复获取同一把锁,而不会被阻塞。

lock.lock();
try {
    lock.lock();   // 可重入,不会死锁
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
} finally {
    lock.unlock();
}

内部通过 计数器(state) 记录加锁次数,释放时逐步递减,直到计数为 0 才真正释放锁。


2.2 内部实现

ReentrantLock 基于 AQS(AbstractQueuedSynchronizer) 实现。

AQS 核心思想
  • 使用 volatile int state 表示锁的状态(0=未锁定,>0=锁重入次数)。
  • 使用 FIFO 等待队列(CLH 队列) 管理未获取到锁的线程。
  • 使用 CAS 操作 实现原子性的 state 修改。

简化流程:

Thread-1Thread-2ReentrantLock(AQS)CAS state=0 → 1 (成功加锁)CAS 失败,进入队列unlock(),state=0唤醒出队,重试获取锁Thread-1Thread-2ReentrantLock(AQS)

2.3 公平锁与非公平锁

  • 非公平锁(默认)

    • 线程尝试直接 CAS 获取锁,不成功才进入队列。
    • 吞吐量高,但可能导致“饥饿”。
  • 公平锁

    • 按照等待时间顺序严格排队,先来先得。
    • 吞吐量较低,但保证公平性。

创建方式:

ReentrantLock fairLock = new ReentrantLock(true);   // 公平锁
ReentrantLock unfairLock = new ReentrantLock();     // 默认非公平锁

2.4 可中断锁

synchronized 不同,ReentrantLock 支持 响应中断

try {
    lock.lockInterruptibly();
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
} catch (InterruptedException e) {
    // 处理中断逻辑
}

这在死锁恢复或任务取消场景下非常有用。


2.5 tryLock 与超时获取

  • 立即尝试获取

    if (lock.tryLock()) {
        try {
            // 临界区
        } finally {
            lock.unlock();
        }
    }
    
  • 带超时的获取

    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            // 临界区
        } finally {
            lock.unlock();
        }
    } else {
        // 超时未获取到锁
    }
    

这种能力在高并发业务中非常实用,避免线程长时间阻塞。


2.6 条件变量(Condition)

相比 synchronized 只能依赖 wait/notify,ReentrantLock 提供了更灵活的 Condition

Condition condition = lock.newCondition();

lock.lock();
try {
    while (!ready) {
        condition.await();   // 进入等待队列
    }
    // 执行业务
    condition.signal();      // 唤醒一个等待线程
} finally {
    lock.unlock();
}
  • 一个锁可以关联 多个 Condition 对象,避免单一 waitSet 的局限。
  • 更利于复杂场景下的线程协调。

三、典型应用场景

  1. 需要超时获取锁的业务逻辑

    • 例如分布式环境下,避免无限等待资源。
  2. 响应中断的锁等待

    • 可用于任务取消、线程池关闭时的安全退出。
  3. 精细化线程协作

    • 使用多个 Condition,代替 synchronized + wait/notify,提升代码可读性。
  4. 高并发竞争下

    • 非公平锁模式下吞吐量优于 synchronized

四、使用建议

  1. 优先使用 synchronized

    • 在语义清晰、功能足够时,推荐首选 synchronized(性能已不再是短板)。
  2. 在以下情况使用 ReentrantLock

    • 需要 公平性保证
    • 需要 可中断锁获取
    • 需要 超时锁获取
    • 需要 多个条件变量
    • 需要更复杂的同步控制。
  3. 注意 unlock() 必须写在 finally 块中,否则可能造成锁泄漏。


五、总结

  • ReentrantLock 是基于 AQS 的显式锁,实现了 可重入、公平/非公平选择、可中断、tryLock、条件变量 等高级功能。
  • synchronized 相比,它提供了更多的灵活性和可控性,但需要开发者谨慎管理锁的获取与释放。
  • 在工程实践中,推荐以 synchronized 为首选,仅在业务需要时引入 ReentrantLock。

一句话总结:
synchronized 语义清晰、简单易用,ReentrantLock 功能丰富、灵活可控。选择哪一种,取决于业务复杂度与工程场景。

posted @ 2025-09-04 16:50  NeoLshu  阅读(3)  评论(0)    收藏  举报  来源