详解 ReentrantLock

本文详细探讨了ReentrantLock的重入性和公平性原理,包括其支持的公平锁和非公平锁的区别,以及如何实现线程对共享资源的重入加锁。

1. ReentrantLock的介绍

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。在java关键字 synchronized 隐式支持重入性(关于synchronized可以看这篇文章),synchronized通过获取自增,释放自减的方式实现重入。

与此同时,ReentrantLock还支持公平锁非公平锁两种方式。

那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:

1. 重入性的实现原理;

2. 公平锁和非公平锁。

2. 重入性的实现原理

要想支持重入性,就要解决两个问题:1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。通过这篇文章,我们知道,同步组件主要是通过重写AQS的几个protected方法来表达自己的同步语义。针对第一个问题,我们来看看ReentrantLock是怎样实现的,以非公平锁为例,判断当前线程能否获得锁为例,核心方法为nonfairTryAcquire:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 如果该锁未被任何线程占有,该锁能被当前线程获取
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //2.若被占有,检查占有线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
        // 3. 再次获取,计数加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

这段代码的逻辑也很简单,具体请看注释。为了支持重入性,在第二步增加了处理逻辑,如果该锁已经被线程所占有了,会继续检查占有线程是否为当前线程,如果是的话,同步状态加1返回true,表示可以再次获取成功。每次重新获取都会对同步状态进行加一的操作,那么释放的时候处理思路是怎样的了?(依然还是以非公平锁为例)核心方法为tryRelease:

protected final boolean tryRelease(int releases) {
    //1. 同步状态减1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        //2. 只有当同步状态为0时,锁成功被释放,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 3. 锁未被完全释放,返回false
    setState(c);
    return free;
}

代码的逻辑请看注释,需要注意的是,重入锁的释放必须得等到同步状态为0时锁才算成功释放,否则锁仍未释放。如果锁被获取n次,释放了n-1次,该锁未完全释放返回false,只有被释放n次才算成功释放,返回true。到现在我们可以理清ReentrantLock重入性的实现了,也就是理解了同步语义的第一条。

3. 公平锁与公平锁

ReentrantLock支持两种锁:公平锁非公平锁何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。ReentrantLock的构造方法无参时是构造非公平锁,源码为:

public ReentrantLock() {
    sync = new NonfairSync();
}

另外还提供了另外一种方式,可传入一个boolean值,true时为公平锁,false时为非公平锁,源码为:

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

在上面非公平锁获取时(nonfairTryAcquire方法)只是简单的获取了一下当前状态做了一些逻辑处理,并没有考虑到当前同步队列中线程等待的情况。我们来看看公平锁的处理逻辑是怎样的,核心方法为:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
  }
}

这段代码的逻辑与nonfairTryAcquire基本上一致,唯一的不同在于增加了hasQueuedPredecessors的逻辑判断,方法名就可知道该方法用来判断当前节点在同步队列中是否有前驱节点的判断,如果有前驱节点说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点的话,再才有做后面的逻辑判断的必要性。公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁

公平锁 VS 非公平

  1. 公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象

  2. 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量

https://www.jianshu.com/p/dc5602eafd51

<think>好的,我现在需要详细解释Java中ReentrantLock的加锁机制使用方法。首先,用户提供的引用资料中提到了ReentrantLock的基本用法,比如lock()unlock()方法,以及其与AQS(AbstractQueuedSynchronizer)的关系。我需要将这些信息整合起来,形成一个清晰的讲解结构。 首先,应该从ReentrantLock的基本概念入手,说明它是一个可重入的互斥锁,具有与synchronized相似的功能,但更灵活。然后,引用示例代码展示基本用法,比如在doSomething方法中加锁解锁的过程。这里要注意代码的正确性,比如在finally块中释放锁,确保异常时也能解锁。 接下来,需要深入加锁机制。根据引用[3],ReentrantLock的lock()方法调用了sync.lock(),而sync是继承自AQS的内部类。这里要区分公平锁非公平锁的不同实现。非公平锁会直接尝试获取锁,如果失败则进入AQS队列;公平锁则会先检查队列是否有等待线程。需要详细解释这两个过程,并结合源码片段,如compareAndSetState(0,1)来展示CAS操作。 然后,可重入性的实现是关键点。要说明锁内部维护了一个计数器,记录线程重入的次数,每次lock()计数器加1,unlock()减1,直到为0时释放锁。这部分需要例子说明,比如同一个线程多次加锁的情况。 此外,使用注意事项也很重要,比如确保在finally中释放锁,避免死锁。还可以提到ReentrantLock的高级功能,如尝试获取锁、可中断锁等,但根据用户的问题,可能需要简要提及,保持重点在基本机制用法。 最后,生成相关问题时要围绕ReentrantLock的特性、AQS的作用、公平与非公平锁的区别、使用注意事项等,确保问题能帮助用户进一步理解。 需要检查引用资料的正确性,比如引用[4]提到Doug Lea的贡献AQS,可以用来支持AQS的讲解部分。同时,确保各部分引用标注正确,比如源码部分引用[3][4]。 现在组织内容的结构:概述、基本用法、加锁机制(公平与非公平)、可重入性、注意事项、相关问题。确保每个部分都有引用支持,并且语言简洁明了,符合用户要求的中文回答,使用正确的LaTeX格式,如代码块用反引号包裹,行内公式用$符号。</think>### ReentrantLock加锁机制及使用详解 #### 1. 基本概念 `ReentrantLock`是Java提供的可重入互斥锁,属于`java.util.concurrent.locks`包。与`synchronized`关键字相比,它支持**公平锁策略**、**可中断锁****超时获取锁**等特性[^4]。 #### 2. 基本用法 通过`lock()`加锁,`unlock()`释放锁,需在`finally`块中确保释放: ```java private ReentrantLock lock = new ReentrantLock(); public void doSomething() { lock.lock(); // 加锁 try { // 临界区代码 } finally { lock.unlock(); // 释放锁 } } ``` #### 3. 加锁机制 ##### 3.1 非公平锁(默认) 调用`lock()`时通过CAS操作尝试直接获取锁: ```java final void lock() { if (compareAndSetState(0, 1)) // CAS操作 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // 进入AQS队列等待 } ``` 若失败则调用`acquire()`,进入AQS队列排队[^3]。 ##### 3.2 公平锁 构造函数传入`true`启用公平锁: ```java ReentrantLock fairLock = new ReentrantLock(true); ``` 公平锁会先检查AQS队列是否有等待线程,避免插队[^4]。 #### 4. 可重入性实现 通过`state`变量记录重入次数: ```java protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { // 同一线程重入 int nextc = c + acquires; setState(nextc); // 增加重入次数 return true; } return false; } ``` #### 5. 注意事项 1. **必须手动释放锁**:`unlock()`需在`finally`中调用,避免死锁 2. **避免锁嵌套**:重入次数需与释放次数严格匹配 3. **性能权衡**:公平锁保证顺序但吞吐量低,非公平锁反之 #### 6. 典型应用场景 - 需要细粒度控制的并发资源访问 - 实现带超时的锁获取(`tryLock(long timeout, TimeUnit unit)`) - 处理可中断的阻塞操作(`lockInterruptibly()`)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值