ReentrantLock介绍及使用(超详细)

深入理解JavaReentrantLock:原理、应用与实战案例
ReentrantLock是一种可重入的互斥锁,提供公平锁和非公平锁的选择,支持中断和超时。其优点包括可中断、可设置超时、多条件绑定等。文章介绍了ReentrantLock的使用注意事项,如避免锁粒度过大、保持加锁顺序防止死锁,并提供了实际应用案例,包括处理方法超时和演示公平锁与非公平锁的区别。
本文章已经生成可运行项目,

点击 Mr.绵羊的知识星球 解锁更多优质文章。

目录

一、介绍

1. 简介

2. 是什么类型的锁

3. 优点

4. 原理

5. 主要方法

6. 使用时注意事项

二、实际应用

1. 案例一

2. 案例二


一、介绍

1. 简介

    ReentrantLock是一种基于AQS(Abstract Queued Synchronizer)框架的应用实现,是JDK中一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

2. 是什么类型的锁

    (1) 公平锁或非公平锁(下面案例中有实际代码,自己执行一遍更容易理解)

    a. 公平锁:先来的线程先执行,排成排按顺序。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

    b. 非公平锁:后来的线程有可能先执行,可插队不一定按顺序。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

    (2) 互斥锁

    一次只能执行一个线程。

    (3) 可重入锁

    同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

    比较抽象,解释下:假如景区有3个收费项目,如果每个项目单独玩都需要收费。但是你买了个VIP。进入景区大门的时候工作人员为了方便,直接给你挂了个牌子,里面三个项目的工作人员看到你的牌子,就认为你已经买过该项目的门票,不在向你收费,进去就行。

3. 优点

    (1) 可中断并且可以设置超时时间。

    (2) 可以根据业务场景使用公平锁或非公平锁。

    (3) 获取锁可设置超时。

    (4) 可绑定多个条件(Condition)。

   关于他的优点肯定是和其他锁比较得来的,一般都是和synchronized(synchronized介绍及使用 这篇文章稍后上传)比较。

4. 原理

    ReentrantLock是怎么实现锁同步的呢?咱们先看代码:

00d350814d0a461d8fa563a2181402b5.png

0abbe90e634342748edb486e57b9ea8f.png

669d3f4ca638430aaf370ce98b634bb5.png

129186ac9a744562b0e21da79373f85f.png

edd21c08f7fe409ab252fd846bbc1c0b.png

   点来点去发现,具体的加锁逻辑的实现在AQS这个抽象类中,所以想要了解其实现原理,还得看看AbstractQueuedSynchronizer(AQS详解 这篇文章稍后上传)

5. 主要方法

    getHoldCount():当前线程调用 lock() 方法的次数。

    getQueueLength():当前正在等待获取 Lock 锁的线程的估计数。

    getWaitQueueLength(Condition condition):当前正在等待状态的线程的估计数,需要传入 Condition 对象。

    hasWaiters(Condition condition):查询是否有线程正在等待与 Lock 锁有关的 Condition 条件。

    hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取 Lock 锁。

    hasQueuedThreads():查询是否有线程正在等待获取此锁定。

    isFair():判断当前 Lock 锁是不是公平锁。

    isHeldByCurrentThread():查询当前线程是否保持此锁定。

    isLocked():查询此锁定是否由任意线程保持。

    tryLock():线程尝试获取锁,如果获取成功,则返回 true,如果获取失败(即锁已被其他线程获取),则返回 false。

    tryLock(long timeout,TimeUnit unit):线程如果在指定等待时间内获得了锁,就返回true,否则返回 false。

    lockInterruptibly():如果当前线程未被中断,则获取该锁定,如果已经被中断则出现异常。

    方法详细介绍可以看最下面参考文章:ReentrantLock类中的方法

6. 使用时注意事项

    (1) 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

    说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。

    (2) 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。

    说明:线程一需要对表A、B、C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是A、B、C,否则可能出现死锁。

    (3) 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。

    a. 在lock方法与try代码块之间的方法调用抛出异常,无法解锁,造成其它线程无法成功获取锁。

    b. 如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。

    c. 在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。

    d. 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。

    说明:Lock对象的unlock方法在执行时,它会调用AQS的tryRelease方法(取决于具体实现类),如果当前线程不持有锁,则抛出IllegalMonitorStateException异常。

上面就是阿里规约中对锁的使用一些注意事项,感兴趣可以学习下!阿里巴巴编码规范学习及应用

二、实际应用

    某个方法或者共享变量不能被多个线程同时操作,可以用ReentrantLock进行加锁。当然这只是其中的一种解决方式。

    注意:使用之前你要知道,使用锁的话执行会相对较慢,因为在加锁的代码块内,每次只能执行一个线程。举个栗子(简单看看就行。git地址):

public static void main(String[] args) {
    // 设置执行次数
    int executeCount = 2;
    // 定义方法开始时间,单位: ms
    long startTime = System.currentTimeMillis();
    // java.util.concurrent提供的API,在该案例中主要是为了让主线程等待子线程结束后进行打印
    CountDownLatch countDownLatch = new CountDownLatch(executeCount);

    ReentrantLock lock = new ReentrantLock();
    for (int index = 0; index < executeCount; index++) {
        new Thread(() -> {
            LOGGER.info("current thread name: {} start.", Thread.currentThread().getName());
            // 加锁
            lock.lock();
            try {
                // 休眠1秒
                sleep(1);
            } finally {
                // 一定要在finally解锁
                lock.unlock();
            }

            /// 休眠1秒,如果测试不加锁耗时可将注释打开并对上面加锁逻辑进行注释
            // sleep(1);
            LOGGER.info("current thread name: {} end.", Thread.currentThread().getName());
            countDownLatch.countDown();
        }).start();
    }

    try {
        // 主线程等待子线程结束后打印log信息
        countDownLatch.await();
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    LOGGER.info("cost time: {}ms", System.currentTimeMillis() - startTime);
}

通过上面的案例可知:加锁耗时2075ms,不加锁耗时1058ms。这有点耗时啊,所以如果必须加锁的话一定要把锁的范围控制在最小,避免浪费太多时间。但是这个加锁方式有个小问题,什么问题呢?我们继续往下看!

1. 案例一

(1) 场景:

    王涛是公司的开发人员,他写了一个远程调用的方法,并且在调用这个方法前加了锁,调用之后解锁。他在本地测试没啥问题,所以就上线了。突然有一天很多客户点击王涛写的方法,结果发现一直加载中...没有任何提示。这线上问题一发生,搞的王涛和经理连夜加班找bug,之后进行如下修改。

(2) 代码:git地址

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLockCase1
 * 使用ReentrantLock调用方法超时处理
 *
 * @author wxy
 * @date 2023-02-16
 */
public class ReentrantLockCase1 {
    private static final Logger LOGGER = LoggerFactory.getLogger(ReentrantLockCase1.class);

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        for (int index = 0; index < 2; index++) {
            new Thread(() -> {
                /*---修改前代码---*/
                /*try {
                    lock.lock();
                    // 调用某个方法,这个方法会超时60s(偶现)
                    timeoutApi();
                } finally {
                    lock.unlock();
                }*/

                /*---修改后代码---*/
                try {
                    // 设置如果线程1正在调用,线程2等待5秒,5秒后你可以对线程2进行处理: 比如返回提示、线程处理...
                    // 如果你不设置超时时间,那么所有的线程就会等待前一个线程解锁,具体怎么等待请看AQS详解
                    // 备注: 正常情况下超时时间应该在配置文件中配置,可以按照业务随时进行调整
                    if (lock.tryLock(5, TimeUnit.SECONDS)) {
                        // 调用某个方法,这个方法会超时60s(偶现)
                        timeoutApi();
                    } else {
                        /// 你可以写一些业务逻辑,来处理超时的线程2和超时期间的后续线程
                        LOGGER.info("operation timeout");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    // 一定要在finally解锁
                    lock.unlock();
                }
            }).start();
        }
    }

    /**
     * 超时的API
     */
    private static void timeoutApi() {
        LOGGER.info("timeout api start.");
        sleep(60);
        LOGGER.info("timeout api end.");
    }

    /**
     * 设置超时时间
     *
     * @param timeOut 超时时间(秒)
     */
    private static void sleep(long timeOut) {
        try {
            TimeUnit.SECONDS.sleep(timeOut);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

    当然,这是一种处理方式,还有其他的处理方式,欢迎评论区留言。

2. 案例二

(1) 场景

    写了一下公平锁和非公平锁在执行过程中的打印。

(2) 代码:git地址

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLockCase2
 * 演示公平锁和非公平锁区别
 *
 * @author wxy
 * @date 2023-02-16
 */
public class ReentrantLockCase2 {
    private static final Logger LOGGER = LoggerFactory.getLogger(ReentrantLockCase1.class);

    public static void main(String[] args) {
        /*
        公平锁,执行结果:
        Thread-1, num : 1
        Thread-0, num : 1
        Thread-3, num : 1
        Thread-2, num : 1
        Thread-4, num : 1
        Thread-1, num : 2
        Thread-0, num : 2
        Thread-3, num : 2
        Thread-2, num : 2
        Thread-4, num : 2

        5个线程很有规律,你一次我一次。
         */
        ReentrantLock lock = new ReentrantLock(true);

        /*
        非公平锁,执行结果:
        Thread-0, num : 1
        Thread-0, num : 2
        Thread-1, num : 1
        Thread-1, num : 2
        Thread-2, num : 1
        Thread-2, num : 2
        Thread-3, num : 1
        Thread-3, num : 2
        Thread-4, num : 1
        Thread-4, num : 2

        一个线程执行完毕后,下一个线程才能开始执行。为什么出现这样的情况呢?
        非公平锁就是谁能抢谁先来,由于Thread-0线程刚执行完一次,线程还处于活跃状态,
        而其他线程需要被唤醒才能执行,所以相对于其他线程来说他更活跃,所以再次抢到锁并执行。
         */
        // ReentrantLock lock = new ReentrantLock(false);
        for (int index = 0; index < 5; index++) {
            new Thread(() -> {
                for (int num = 1; num <= 2; num++) {
                    lock.lock();
                    try {
                        LOGGER.info("thread name: {}, num : {}", Thread.currentThread().getName(), num);
                    } finally {
                        lock.unlock();
                    }
                }
            }).start();
        }
    }
}

参考文章

    1. ReentrantLock类中的方法

 

本文已生成可运行项目
<think>我们被要求介绍ReentrantLock的源码和使用方法。根据引用内容,ReentrantLockJava中一个重要的锁实现,它提供了比synchronized更灵活的锁操作。我们将从以下几个方面进行介绍: 1. ReentrantLock概述 2. 核心源码解析(包括Sync、NonfairSync、FairSync) 3. 使用方法 4. 与synchronized的对比 5. 总结 注意:由于源码解析可能较长,我们将重点放在关键部分,如加锁和解锁过程。 # ReentrantLock源码解析及其使用方法 ## 一、ReentrantLock概述 `ReentrantLock`是`java.util.concurrent.locks`包下的一个可重入互斥锁,实现了`Lock`接口。它提供了与`synchronized`关键字相同的互斥性和内存可见性,但增加了可中断的锁获取、超时获取锁、公平锁等高级功能[^1]。 **核心特性**: - **可重入性**:同一个线程可以多次获得同一把锁。 - **公平性选择**:支持公平锁和非公平锁(默认非公平)。 - **锁获取的灵活性**:提供了`tryLock()`、`lockInterruptibly()`等方法。 ## 二、源码解析 ### 1. 类结构 ```java public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; // 同步器,继承自AQS abstract static class Sync extends AbstractQueuedSynchronizer { ... } static final class NonfairSync extends Sync { ... } // 非公平锁 static final class FairSync extends Sync { ... } // 公平锁 } ``` - **Sync**:继承自`AbstractQueuedSynchronizer`(AQS),是锁实现的核心。 - **NonfairSync**:非公平锁实现(默认)。 - **FairSync**:公平锁实现。 ### 2. 非公平锁加锁过程(`NonfairSync.lock()`) ```java final void lock() { if (compareAndSetState(0, 1)) // 尝试CAS快速获取锁 setExclusiveOwnerThread(Thread.currentThread()); // 成功则设置当前线程为独占线程 else acquire(1); // 进入AQS的acquire方法 } // AQS的acquire方法 public final void acquire(int arg) { if (!tryAcquire(arg) && // 再次尝试获取锁(非公平体现) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入同步队列并阻塞 selfInterrupt(); } // NonfairSync的tryAcquire实现 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // Sync中的nonfairTryAcquire final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 锁未被占用 if (compareAndSetState(0, acquires)) { // CAS尝试获取 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; } ``` **非公平性体现**:线程在请求锁时,先尝试一次CAS获取(不检查等待队列),失败后再进入AQS的同步队列排队。 ### 3. 公平锁加锁过程(`FairSync.lock()`) ```java final void lock() { acquire(1); // 直接进入AQS的acquire,不会尝试插队 } 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()) { // 重入 // ... 同非公平锁 } return false; } ``` **公平性体现**:在获取锁时,先检查同步队列中是否有等待的线程(`hasQueuedPredecessors()`),如果有则放弃获取。 ### 4. 解锁过程(`unlock()`) ```java public void unlock() { sync.release(1); // 调用AQS的release } // AQS的release public final boolean release(int arg) { if (tryRelease(arg)) { // 尝试释放锁 Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); // 唤醒后继节点 return true; } return false; } // Sync中的tryRelease 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; } ``` **重入计数**:每次释放锁,状态值`state`减1,直到为0才完全释放。 ## 三、使用方法 ### 1. 基本使用模板 ```java ReentrantLock lock = new ReentrantLock(); // 默认非公平锁 // ReentrantLock lock = new ReentrantLock(true); // 公平锁 lock.lock(); // 阻塞获取锁 try { // 临界区代码 } finally { lock.unlock(); // 必须在finally中释放锁 } ``` ### 2. 高级用法 #### (1) 尝试获取锁(`tryLock()`) ```java if (lock.tryLock()) { // 立即返回是否成功 try { /* 操作 */ } finally { lock.unlock(); } } else { // 执行其他操作 } // 超时等待 if (lock.tryLock(1, TimeUnit.SECONDS)) { try { /* 操作 */ } finally { lock.unlock(); } } else { // 超时处理 } ``` #### (2) 可中断获取锁(`lockInterruptibly()`) ```java try { lock.lockInterruptibly(); // 可响应中断 try { /* 操作 */ } finally { lock.unlock(); } } catch (InterruptedException e) { // 中断处理 Thread.currentThread().interrupt(); // 恢复中断状态 } ``` #### (3) 条件变量(`Condition`) ```java Condition condition = lock.newCondition(); // 线程1:等待条件 lock.lock(); try { while (!conditionSatisfied) { condition.await(); // 释放锁并等待 } // 条件满足后执行 } finally { lock.unlock(); } // 线程2:唤醒 lock.lock(); try { conditionSatisfied = true; condition.signal(); // 唤醒一个等待线程 } finally { lock.unlock(); } ``` ## 四、与synchronized对比 | **特性** | **ReentrantLock** | **synchronized** | |-------------------|------------------------------------|---------------------------| | **实现方式** | Java API(AQS) | JVM关键字(monitor) | | **锁释放** | 必须手动`unlock()` | 自动释放 | | **可中断** | 支持(`lockInterruptibly()`) | 不支持 | | **公平性** | 支持(构造参数指定) | 仅非公平 | | **条件变量** | 支持多个`Condition` | 单一等待队列 | | **锁绑定** | 可跨方法释放 | 方法/代码块内 | ## 五、总结 - **优先使用场景**: - 需要高级功能(如可中断、超时、公平锁)时选择`ReentrantLock`。 - 简单同步场景仍可使用`synchronized`(代码简洁且自动释放)。 - **注意事项**: - 必须使用`try-finally`确保锁释放,避免死锁。 - 公平锁会降低吞吐量,除非必要否则使用非公平锁。 > 📖 **设计思想启示**:`ReentrantLock`通过AQS实现,将锁的获取与释放逻辑交给子类实现(模板方法模式),同时面向接口设计(`Lock`)隐藏了实现细节,使锁的使用与实现解耦[^2][^5]。 --- ### 相关问题 1. `ReentrantLock`的公平锁和非公平锁在性能上有何差异? 2. 如何通过AQS自定义实现一个锁? 3. `ReentrantLock`与`synchronized`在重入实现上有何异同? 4. 为什么`ReentrantLock`的默认实现是非公平锁?</think># ReentrantLock源码解析与使用指南 ## 一、ReentrantLock核心概述 `ReentrantLock`是Java并发包(`java.util.concurrent.locks`)中的**可重入互斥锁**实现,它提供了比`synchronized`更灵活的锁控制能力。主要特性包括: 1. **可重入性**:同一线程可多次获取同一把锁 2. **公平性选择**:支持公平锁与非公平锁(默认非公平) 3. **锁获取灵活性**:提供`tryLock()`、`lockInterruptibly()`等高级方法 4. **条件变量支持**:可通过`newCondition()`创建多个等待队列 ```java // 基本使用示例 ReentrantLock lock = new ReentrantLock(); lock.lock(); // 获取锁 try { // 临界区代码 } finally { lock.unlock(); // 必须手动释放 } ``` ## 二、核心源码解析 ### 1. 类结构体系 ```mermaid classDiagram class Lock { <<interface>> +lock() +unlock() +tryLock() } class ReentrantLock { -Sync sync +ReentrantLock() +ReentrantLock(boolean fair) } class Sync { <<abstract>> +nonfairTryAcquire() +tryRelease() } class NonfairSync { +lock() +tryAcquire() } class FairSync { +lock() +tryAcquire() } Lock <|.. ReentrantLock ReentrantLock *-- Sync Sync <|-- NonfairSync Sync <|-- FairSync ``` ### 2. 关键组件解析 #### (1) Sync同步器(继承AQS) ```java abstract static class Sync extends AbstractQueuedSynchronizer { // 非公平获取尝试 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 获取锁状态 if (c == 0) { if (compareAndSetState(0, acquires)) { // CAS尝试获取锁 setExclusiveOwnerThread(current); // 设置独占线程 return true; } } // 重入逻辑:当前线程已持有锁 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; // 增加重入计数 setState(nextc); return true; } return false; } // 释放锁实现 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; } } ``` #### (2) 非公平锁实现(NonfairSync) ```java static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) // 尝试直接获取锁(插队) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // 进入AQS队列 } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); // 调用父类非公平获取 } } ``` #### (3) 公平锁实现(FairSync) ```java static final class FairSync extends Sync { final void lock() { acquire(1); // 直接进入队列(不插队) } 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()) { // ... } return false; } } ``` ### 3. AQS同步队列工作原理 ```mermaid graph LR A[线程尝试获取锁] --> B{锁是否空闲?} B -->|是| C[CAS获取锁] B -->|否| D[加入CLH队列尾部] D --> E[进入阻塞状态] C --> F[执行临界区代码] F --> G[释放锁] G --> H[唤醒队列中下一个线程] ``` ## 三、高级使用技巧 ### 1. 锁获取的三种方式 ```java ReentrantLock lock = new ReentrantLock(); // 1. 阻塞获取 lock.lock(); // 2. 可中断获取 try { lock.lockInterruptibly(); // 可响应中断 } catch (InterruptedException e) { // 处理中断 } // 3. 尝试获取 if (lock.tryLock()) { // 立即返回 // 获取成功 } else { // 执行备选方案 } // 带超时尝试 if (lock.tryLock(1, TimeUnit.SECONDS)) { // ... } ``` ### 2. 条件变量(Condition)使用 ```java ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // 创建条件变量 // 等待线程 lock.lock(); try { while (!conditionSatisfied) { condition.await(); // 释放锁并等待 } // 条件满足后执行 } finally { lock.unlock(); } // 通知线程 lock.lock(); try { conditionSatisfied = true; condition.signalAll(); // 唤醒所有等待线程 } finally { lock.unlock(); } ``` ### 3. 公平锁与非公平锁选择 ```java // 非公平锁(默认,吞吐量高) ReentrantLock unfairLock = new ReentrantLock(); // 公平锁(减少线程饥饿,吞吐量较低) ReentrantLock fairLock = new ReentrantLock(true); ``` ## 四、性能优化与最佳实践 1. **锁释放保证**:必须将`unlock()`放在`finally`块中 ```java lock.lock(); try { // 临界区 } finally { lock.unlock(); // 确保锁释放 } ``` 2. **避免嵌套锁**:防止死锁发生 ```java // 危险示例(易死锁) public void methodA() { lock.lock(); try { methodB(); // 内部再次获取锁 } finally { lock.unlock(); } } public void methodB() { lock.lock(); // 重入安全,但设计不推荐 try { ... } finally { lock.unlock(); } } ``` 3. **锁粒度控制**:跨方法加锁示例 ```java public class ResourceManager { private final ReentrantLock lock = new ReentrantLock(); public void initResource() { lock.lock(); // ...初始化资源 // 注意:不在该方法解锁 } public void releaseResource() { try { /* 使用资源 */ } finally { lock.unlock(); } // 在另一方法释放 } } ``` ## 五、与synchronized对比 | **特性** | **ReentrantLock** | **synchronized** | |-------------------|------------------------------------|---------------------------| | **实现机制** | Java API(基于AQS) | JVM内置(monitor) | | **锁释放** | 必须手动`unlock()` | 自动释放 | | **公平性** | 支持(构造参数指定) | 仅非公平 | | **可中断性** | 支持(`lockInterruptibly()`) | 不支持 | | **条件变量** | 支持多个`Condition` | 单一等待队列 | | **性能** | 高竞争场景更优 | 低竞争场景更优 | ## 六、适用场景 1. **需要高级功能**:锁超时、可中断锁获取 2. **公平性要求**:需要按请求顺序获取锁 3. **跨方法锁控制**:在多个方法中持有和释放锁 4. **读写分离需求**:结合`ReentrantReadWriteLock`使用 5. **高竞争环境**:减少线程上下文切换开销 > **设计思想启示**:ReentrantLock通过面向接口设计(实现Lock接口)和AQS抽象,**将锁的使用与实现分离**,使开发者无需关注底层同步细节即可安全使用[^2][^5]。这种设计既保证了易用性,又提供了灵活的扩展能力。 --- ### 相关问题 1. ReentrantLock的公平锁与非公平锁在性能上有何差异? 2. 如何通过AQS实现自定义同步器? 3. Condition条件变量与Object.wait()/notify()有何异同? 4. ReentrantLock在死锁检测与恢复方面有何优势? 5. JDK中哪些并发工具基于AQS实现?
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懒阳快跑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值