ReentrantLock的加锁和解锁原理

本文详细解析了ReentrantLock的内部实现,包括公平锁和非公平锁的工作原理,以及它们如何依赖于AQS框架进行加锁和解锁操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ReentrantLock的实现依赖于Java同步器框架AQS。
AQS使用一个整型的volatile变量(命名为state)来维护同步状态。

  • ReentrantLock分为公平锁和非公平锁

公平锁

使用公平锁时,加锁方法lock()的调用轨迹:

  1. ReentrantLock:lock()
  2. FairSync:lock()
  3. AbstractQueuedSynchronizer:acquire(int arg)
  4. ReentrantLock:tryAcquire(int acquires)

第4步开始加锁,源代码如下

protected final boolean tryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //通过AQS获取同步状态
            int c = getState();
            //同步状态为0,说明临界区处于无锁状态,
            if (c == 0) {
                //判断该线程是否在队首,然后修改同步状态,即加锁
                if (isFirst(current)&&compareAndSetState(0, acquires)) {
                    //将当前线程设置为锁的owner
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果临界区处于锁定状态,且上次获取锁的线程为当前线程
            else if (current == getExclusiveOwnerThread()) {
                 //则递增同步状态
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

其解锁方法unlock()调用轨迹如下:

  1. ReentrantLock:unlock()
  2. AbstractQueuedSynchronizer:release(int arg)
  3. Sync:tryRelease(int releases)

第3步才开始真正释放锁

protected final boolean tryRelease(int releases){
    int c = getState() - releases;
    if(Tread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException()
    boolean free = false;
    if(c == 0){//如果state减去releases为0,则释放锁
        free = true;
        setExcluiveOwnerThread(null);//将获取锁的线程设为null,即无锁
    }
    setState(c);//设置state,若c为0,则释放锁
    return free;
}

非公平锁

非公平锁的释放与公平锁一样,这里仅探讨锁的获取。

使用非公平锁时,加锁方法lock()调用轨迹如下:

  1. ReentrantLock:lock()
  2. NonfairSync:lock()
  3. AbstractQueuedSynchronizer:compareAndSetState(int expect,int update)
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        
        if (compareAndSetState(0, 1))//对state进行CAS
            //若成功则加锁   
            setExclusiveOwnerThread(Thread.currentThread();
        else
            acquire(1);
    }
}

第3步进行加锁。
该方法使用原子操作的方式更新state变量。

总结:

  • 公平锁和非公平锁的释放时,最后都要写一个volatile变量state。
  • 公平锁获取时,首先会读volatile变量。
  • 非公平锁的获取,首先会用CAS更新volatile变量,这个操作同时具有volatlie写和volatile读的内存语义。
<think>我们正在讨论Java中的Lock接口,它提供了一种更灵活的同步机制。与synchronized关键字不同,Lock接口需要显式地加锁解锁。让我们通过分析引用内容来理解Lock接口的使用机制。引用[1]展示了一个使用Lock的示例:通过ReentrantLock实现的获取释放。引用[2]指出Lock是一个接口,而synchronized是内置的关键字,并提到Lock需要在finally块中释放以避免死。引用[3]则解释了ReentrantLock支持重入公平性选择。现在,我将详细解释Lock接口的加锁解锁机制:1.**Lock接口的基本使用**-创建Lock实例(通常使用ReentrantLock)-在访问共享资源前调用lock()方法获取-在finally块中调用unlock()方法释放2.**重入性**-同一个线程可以多次获取同一把(不会阻塞自己)-重入次数必须与释放次数匹配3.**公平性**-创建ReentrantLock时可指定公平策略-公平:等待时间最长的线程优先获取-公平:允许线程"插队"4.**与synchronized的对比**-Lock提供更细粒度的控制(如可中断、超时获取等)-必须手动释放,否则会导致死示例代码演示:```javaimportjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;publicclassLockExample{privatefinalLocklock=newReentrantLock();//创建实例publicvoidsafeMethod(){lock.lock();//获取(阻塞直到成功)try{//临界区代码System.out.println(Thread.currentThread().getName()+"进入临界区");Thread.sleep(100);//模拟操作}catch(InterruptedExceptione){Thread.currentThread().interrupt();}finally{lock.unlock();//必须确保释放System.out.println(Thread.currentThread().getName()+"释放");}}publicstaticvoidmain(String[]args){LockExampleexample=newLockExample();//启动多个线程测试for(inti=0;i<3;i++){newThread(example::safeMethod).start();}}}```运行结果将显示线程依次进入临界区,证明的互斥性:```Thread-0进入临界区Thread-0释放Thread-1进入临界区Thread-1释放Thread-2进入临界区Thread-2释放```**关键注意事项**:-必须在finally块中释放(确保异常时也能释放)-避免将lock()调用放在try块内(防止未加锁就执行unlock)-使用可中断时注意处理InterruptedException通过Lock接口,开发者可以更灵活地控制并发,但同时也增加了编程复杂度[^1][^2][^3]。§§相关问题§§1.Lock接口中tryLock()方法的工作原理是什么?2.如何通过Lock实现条件等待(Condition)?3.在分布式环境中如何实现类似Lock的机制?</think>### JavaLock接口的加锁解锁机制详解 Lock接口是Java并发包中提供的显式机制,与`synchronized`关键字相比,它提供了更灵活、更强大的线程同步功能。下面通过关键点解析其工作原理: #### 一、Lock接口核心方法 | 方法 | 作用 | 特点 | |--------------------|----------------------------------------------------------------------|--------------------------| | `lock()` | 获取,若被占用则阻塞等待 | 基础阻塞方法 | | `unlock()` | 释放 | **必须**在finally块调用 | | `tryLock()` | 尝试获取,立即返回结果 | 阻塞,可避免死 | | `tryLock(timeout)` | 在指定时间内尝试获取 | 支持超时控制 | | `lockInterruptibly()` | 获取但允许被中断 | 响应线程中断请求 | #### 二、Lock接口工作流程 ```mermaid graph TD A[线程调用lock.lock] --> B{是否可用} B -->|可用| C[获取并继续执行] B -->|不可用| D[线程进入等待队列] C --> E[执行同步代码] E --> F[finally块调用lock.unlock] F --> G[唤醒等待队列中的线程] ``` #### 三、Lock接口关键特性 1. **显式控制** - 需要**手动调用`lock()``unlock()`** - 必须在`finally`块释放(引用[2]指出否则可能导致死)[^2] ```java Lock lock = new ReentrantLock(); lock.lock(); // 手动加锁 try { // 临界区代码 } finally { lock.unlock(); // 必须保证释放 } ``` 2. **可重入性** - 同一线程可重复获取同一把(引用[3]说明与`synchronized`类似)[^3] ```java public void nestedLock() { lock.lock(); // 第一次获取 try { lock.lock(); // 第二次获取(不会阻塞) // ... } finally { lock.unlock(); lock.unlock(); // 需匹配解锁次数 } } ``` 3. **公平性选择** - 通过构造函数控制公平策略: ```java Lock fairLock = new ReentrantLock(true); // 公平 Lock unfairLock = new ReentrantLock(); // 公平(默认) ``` - 公平:按请求顺序分配(引用[3]解释等待时间最久者优先)[^3] 4. **高级功能** - 尝试机制: ```java if (lock.tryLock(1, TimeUnit.SECONDS)) { // 等待1秒 try { /* 操作 */ } finally { lock.unlock(); } } else { // 处理无法获取的情况 } ``` - 可中断: ```java lock.lockInterruptibly(); // 可能抛出InterruptedException ``` #### 四、Lock vs synchronized | 特性 | Lock接口 | synchronized | |--------------------|-----------------------------------|----------------------------| | **获取方式** | 显式调用`lock()` | 隐式(进入同步块自动获取) | | **异常处理** | 需手动释放(否则死)[^2] | 自动释放 | | **公平性** | 可配置 | 公平 | | **超时控制** | 支持`tryLock(timeout)` | 不支持 | | **中断响应** | 支持`lockInterruptibly()` | 不响应中断 | #### 五、最佳实践 1. **释放保证** 始终在`finally`块调用`unlock()`,避免因异常导致泄漏: ```java Lock lock = new ReentrantLock(); lock.lock(); try { // 可能抛出异常的代码 } finally { lock.unlock(); // 绝对执行 } ``` 2. **避免嵌套陷阱** 重入加锁解锁必须严格匹配: ```java lock.lock(); lock.lock(); // 允许 try { ... } finally { lock.unlock(); // 需两次unlock lock.unlock(); } ``` 3. **优先使用tryLock** 在可能发生死的场景中使用超时控制: ```java if (lockA.tryLock(500, TimeUnit.MILLIS)) { try { if (lockB.tryLock(300, TimeUnit.MILLIS)) { // 成功获取双 } } finally { /* 释放 */ } } ``` > **核心总结**:Lock接口通过显式的加锁/解锁操作,提供了比`synchronized`更精细的线程控制能力。其关键优势在于支持公平、可中断超时获取等高级特性(引用[1][^1][^3]),但需要开发者严格遵循"在finally块释放"的原则以避免死风险(引用[2])[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值