跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同?

与Synchronized相比,可重入锁ReentrantLock在实现原理上存在显著的区别。以下是对这两者的实现原理的详细对比:

一、底层机制与实现方式

  1. Synchronized

    • 属于JVM内置锁,通过JVM隐式实现。
    • 每个Java对象都隐式包含一个监视器(Monitor)对象,加锁的过程就是竞争这个监视器对象的过程。
    • 当线程进入synchronized代码块或方法时,会尝试获取对象的监视器锁。如果获取成功,则线程可以执行同步代码;如果获取失败,则线程会被阻塞,直到锁被释放。
  2. ReentrantLock

    • 是基于AQS(AbstractQueuedSynchronizer,队列同步器)实现的。
    • AQS是一个用于构建锁或其他同步组件的基础框架,它内部实现了两个队列:同步队列和条件队列,用于管理线程的等待和唤醒状态。
    • ReentrantLock通过重写AQS的tryAcquire和tryRelease等方法来实现锁的获取和释放。
    • ReentrantLock默认实现为非公平锁,但也可以配置为公平锁。

二、锁的获取与释放

  1. Synchronized

    • 隐式获取和释放锁。线程在进入synchronized代码块或方法时自动获取锁,离开时自动释放锁。
    • 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。
  2. ReentrantLock

    • 显式获取和释放锁。需要程序员手动调用lock()方法来获取锁,调用unlock()方法来释放锁。
    • 在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象。因此,使用ReentrantLock时需要在finally块中释放锁。

三、锁的特性与功能

  1. Synchronized

    • 不可响应中断。一个线程在获取不到锁时会一直等待下去,不能响应中断。
    • 锁是非公平的,即锁的分配是随机的,不保证按照线程请求锁的顺序来分配。
  2. ReentrantLock

    • 可以响应中断。线程在等待锁时可以选择放弃等待,响应中断。
    • 提供公平锁和非公平锁两种模式。公平锁按照线程请求锁的顺序来分配锁,非公平锁则可能允许插队。
    • 提供Condition接口,允许线程在获得锁后等待特定的条件满足后再执行。
    • 可以绑定多个条件变量,通过Condition对象实现更复杂的线程同步需求。

四、性能与适用场景

  1. Synchronized

    • 由于是JVM内置锁,性能通常较高,且使用简单。
    • 适用于不需要特别高的灵活性和控制能力的场景。
  2. ReentrantLock

    • 性能可能因实现细节和锁类型(公平锁/非公平锁)而有所不同。
    • 提供了更多的控制和功能,适用于需要更高灵活性和控制能力的场景。
    • 在高竞争情况下,ReentrantLock的性能可能更好,因为它提供了更多的高级功能,如公平锁、可中断锁等。

综上所述,Synchronized和ReentrantLock在实现原理上存在显著的区别,主要体现在底层机制与实现方式、锁的获取与释放、锁的特性与功能以及性能与适用场景等方面。开发者在选择使用哪种锁时,应根据具体的应用场景和需求进行权衡和选择。

<think>我们重点讨论ReentrantLock,它是Java中一个重要的可重入实现,基于AQS(AbstractQueuedSynchronizer)框架。 根据引用[1][2][3],我们将从底层原理实现机制、使用场景和代码示例几个方面展开。 一、底层原理 ReentrantLock的核心是AQS(AbstractQueuedSynchronizer),它使用一个整型的volatile变量(state)来表示的状态,以及一个FIFO线程等待队列(CLH队列的变体)来管理未获取到的线程。 1.1 可重入实现 当线程第一次获取时,通过CAS操作将state从0设置为1,并记录当前的持有者(exclusiveOwnerThread)。同一线程再次获取时,state递增,释放时递减,直到state为0时完全释放。 1.2 公平与非公平(引用[2]) - 非公平(默认模式):新线程尝试获取时,不管等待队列中是否有其他线程在等待,直接尝试CAS抢。如果失败,则加入队列等待。这种方式可能导致线程饥饿,但性能高。 - 公平:新线程尝试获取时,先检查等待队列中是否有等待线程,如果有则直接加入队列末尾,按顺序获取。保证公平性,但性能较低。 二、实现步骤(以非公平为例) 1. lock()方法: - 尝试通过CAS将state从0设置为1(快速获取) - 如果成功,设置当前线程为持有者 - 如果失败,调用acquire(1)方法(该方法在AQS中) 2. acquire(1)方法: - 首先调用tryAcquire(1)(由子类实现)再次尝试获取(此时可能由于重入而成功) - 如果失败,则调用addWaiter将当前线程加入等待队列(节点模式为独占模式) - 然后调用acquireQueued方法,在队列中自旋或阻塞等待 3. unlock()方法: - 调用tryRelease(1)(由子类实现)释放(state减1,如果减到0则完全释放) - 唤醒队列中的下一个线程(如果有) 三、使用场景 - 需要可重入特性的场景(例如递归调用加) - 需要尝试非阻塞获取(tryLock) - 需要可中断的获取(lockInterruptibly) - 需要公平机制(防止线程饥饿) - 需要绑定多个条件(Condition,比Object的wait/notify更灵活) 四、代码示例 4.1 基本使用 ```java import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); public void criticalSection() { lock.lock(); // 获取 try { // 临界区代码 System.out.println(Thread.currentThread().getName() + " 进入临界区"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); // 释放 System.out.println(Thread.currentThread().getName() + " 离开临界区"); } } public static void main(String[] args) { final ReentrantLockExample example = new ReentrantLockExample(); // 创建多个线程测试 for (int i = 0; i < 5; i++) { new Thread(example::criticalSection).start(); } } } ``` 4.2 可中断 ```java public void interruptibleLock() { try { lock.lockInterruptibly(); // 可中断加 try { // 临界区 } finally { lock.unlock(); } } catch (InterruptedException e) { // 处理中断 Thread.currentThread().interrupt(); } } ``` 4.3 尝试获取(tryLock) ```java public void tryLockExample() { if (lock.tryLock()) { // 尝试获取,立即返回结果 try { // 临界区 } finally { lock.unlock(); } } else { // 执行其他操作 } } // 带超时的tryLock public void tryLockWithTimeout() throws InterruptedException { if (lock.tryLock(2, TimeUnit.SECONDS)) { // 最多等待2秒 try { // 临界区 } finally { lock.unlock(); } } else { // 超时未获取 } } ``` 4.4 公平 ```java // 创建公平 ReentrantLock fairLock = new ReentrantLock(true); public void fairLockExample() { fairLock.lock(); try { // 临界区 } finally { fairLock.unlock(); } } ``` 五、原理总结 - 可重入性:通过记录持有的线程和重入次数(state)实现。 - 公平性:公平严格按照等待队列顺序获取;非公平允许插队,提高吞吐量。 - 释放:必须由持有的线程释放,且次数匹配(state归零)。 注意:必须将unlock()放在finally块中,以确保的释放。 相关问题: 1. ReentrantLock的公平和非公平在性能上有何差异?为什么? 2. ReentrantLock的Condition机制是如何实现的? 3. ReentrantLocksynchronized关键字相比有哪些优势? 4. 为什么ReentrantLock的tryLock方法是非公平的? 5. 在哪些场景下应该使用ReentrantLock而不是synchronized?</think>### Java ReentrantLock 深度解析 #### 一、底层原理 ReentrantLock 的核心是 **AQS(AbstractQueuedSynchronizer)框架**[^1],其实现基于三个关键组件: 1. **volatile int state**:状态标记(0=未定,≥1=定/重入次数) 2. **CLH队列**:双向链表实现的线程等待队列(FIFO) 3. **CAS操作**:通过 `Unsafe.compareAndSwapInt()` 实现原子状态变更 ```mermaid graph LR A[lock.lock] --> B[尝试CAS设置state] B -->|成功| C[设置持有线程=当前线程] B -->|失败| D[加入CLH队列尾部] D --> E[LockSupport.park 阻塞] ``` **公平 vs 非公平** 实现差异[^2]: - **非公平**(默认):新线程直接尝试抢(可能插队) - **公平**:严格按CLH队列顺序获取 #### 二、功能实现步骤 ##### 1. 加流程 ```java final void lock() { // 非公平实现 if (compareAndSetState(0, 1)) // CAS尝试快速获取 setExclusiveOwnerThread(Thread.currentThread()); // 成功则记录持有线程 else acquire(1); // 进入AQS排队流程 } ``` **重入机制**:同一线程重复获取时,state 值递增(state=2 表示重入1次) ##### 2. 解流程 ```java public void unlock() { sync.release(1); // 释放资源 } protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); // 非持有线程禁止解 boolean free = false; if (c == 0) { // 完全释放 free = true; setExclusiveOwnerThread(null); } setState(c); // 更新状态 return free; } ``` #### 三、使用场景与代码实例 ##### 场景1:基础保护 ```java ReentrantLock lock = new ReentrantLock(); private int balance = 100; void transfer(int amount) { lock.lock(); // 获取 try { if (balance >= amount) { balance -= amount; System.out.println("转账成功,余额:" + balance); } } finally { lock.unlock(); // 必须finally释放 } } ``` ##### 场景2:可中断(应对任务取消)[^3] ```java ReentrantLock lock = new ReentrantLock(); void criticalTask() throws InterruptedException { try { lock.lockInterruptibly(); // 可中断加 // 执行敏感操作 } finally { if (lock.isHeldByCurrentThread()) lock.unlock(); } } // 调用线程可执行:thread.interrupt() 中断等待 ``` ##### 场景3:公平(防止线程饥饿) ```java ReentrantLock fairLock = new ReentrantLock(true); // 启用公平模式 void fairAccess() { fairLock.lock(); try { // 资源访问(按请求顺序执行) } finally { fairLock.unlock(); } } ``` ##### 场景4:尝试(避免死) ```java if (lock.tryLock(2, TimeUnit.SECONDS)) { // 最多等待2秒 try { // 成功获取 } finally { lock.unlock(); } } else { System.out.println("获取超时,执行备用方案"); } ``` #### 四、与synchronized对比 | **特性** | `ReentrantLock` | `synchronized` | |------------------------|----------------------------------|--------------------------| | 获取方式 | 显式调用 `lock()`/`unlock()` | 隐式(代码块/方法) | | 可中断性 | ✅ 支持 `lockInterruptibly()` | ❌ 不支持 | | 公平 | ✅ 可配置公平策略 | ❌ 仅非公平 | | 条件变量 | ✅ 支持多个 `Condition` | ❌ 单条件等待 | | 释放 | 必须手动释放(易出错) | 自动释放 | | 性能 | 高竞争场景更优 | Java 6+优化后接近 | #### 五、最佳实践 1. **必须**在 `finally` 块中释放 2. 避免嵌套,防止死(可用 `tryLock` 超时机制) 3. 读多写少场景考虑 `ReadWriteLock` 4. 粒度尽量细化,减少临界区代码 > ⚠️ **典型错误**: > ```java > lock.lock(); > try { > // 可能抛出异常的代码 > } finally { > // 忘记调用 unlock() > } > ``` --- ### 原理总结 1. **可重入性**:通过记录持有线程和重入计数(state)实现[^1] 2. **公平性**:公平严格按CLH队列顺序获取,非公平允许插队[^2] 3. **中断响应**:通过 `Thread.interrupt()` 唤醒等待线程[^3] 4. **性能优化**:CAS操作减少内核态切换,CLH队列降低竞争
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值