Java ReentrantLock的使用与应用实战

 更新时间:2025年09月29日 10:17:40   作者:IT橘子皮  
ReentrantLock是Java并发包中提供的一种可重入互斥锁,它作为synchronized关键字的替代方案,提供了更灵活、更强大的线程同步机制,下面就来介绍一下ReentrantLock的实战使用

ReentrantLock是Java并发包(java.util.concurrent.locks)中提供的一种可重入互斥锁,它作为synchronized关键字的替代方案,提供了更灵活、更强大的线程同步机制。本文将全面解析ReentrantLock的核心特性、实现原理及实际应用场景。

ReentrantLock概述与基本特性

ReentrantLock是Java 5引入的显式锁机制,它基于AQS(AbstractQueuedSynchronizer)框架实现,提供了比synchronized更丰富的功能和控制能力。与synchronized相比,ReentrantLock具有以下显著特点:

  • 可重入性​:同一线程可以多次获得同一把锁而不会被阻塞,每次获取锁后必须释放相同次数的锁
  • 公平性选择​:支持公平锁和非公平锁两种策略,公平锁按照线程请求顺序分配锁,非公平锁允许"插队"以提高吞吐量
  • 灵活的锁获取方式​:提供尝试非阻塞获取锁(tryLock)、可中断获取锁(lockInterruptibly)和超时获取锁(tryLock with timeout)等方法
  • 条件变量支持​:通过Condition接口实现多个等待队列,比synchronized的wait/notify机制更精准

从实现层级看,synchronized是JVM内置的锁机制,通过monitorenter/monitorexit字节码指令实现;而ReentrantLock是JDK API级别的锁,基于AQS框架构建。

ReentrantLock核心方法与使用

基础锁操作

ReentrantLock的基本使用模式遵循"加锁-操作-释放锁"的流程,必须确保在finally块中释放锁:

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

这种显式锁管理相比synchronized需要更多注意,但提供了更精细的控制。

高级锁获取方式

  1. 尝试非阻塞获取锁(tryLock)​​:

    立即返回获取结果,不阻塞线程,适用于避免死锁或快速失败场景:

    if (lock.tryLock()) {
        try {
            // 临界区代码
        } finally {
            lock.unlock();
        }
    } else {
        // 执行备选方案
    }
    
  2. 超时获取锁​:

    在指定时间内尝试获取锁,避免无限期等待:

    if (lock.tryLock(2, TimeUnit.SECONDS)) {
        try {
            // 临界区代码
        } finally {
            lock.unlock();
        }
    }
    
  3. 可中断获取锁(lockInterruptibly)​​:

    允许在等待锁的过程中响应中断信号:

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

锁状态查询

ReentrantLock提供了一系列状态查询方法:

  • isLocked():查询锁是否被持有
  • isHeldByCurrentThread():当前线程是否持有锁
  • getHoldCount():当前线程持有锁的次数(重入次数)
  • getQueueLength():等待获取锁的线程数

ReentrantLock实现原理

AQS框架基础

ReentrantLock的核心实现依赖于AbstractQueuedSynchronizer(AQS),这是一个用于构建锁和同步器的框架。AQS内部维护了:

  • volatile int state:同步状态,对于ReentrantLock,0表示未锁定,>0表示锁定状态及重入次数
  • FIFO线程等待队列:管理获取锁失败的线程

公平锁与非公平锁实现

ReentrantLock通过两种不同的Sync子类实现锁策略:

  1. 非公平锁(默认)​​:

    final void lock() {
        if (compareAndSetState(0, 1))  // 直接尝试抢占
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    

    新请求的线程可以直接插队尝试获取锁,不考虑等待队列

  2. 公平锁​:

    protected final boolean tryAcquire(int acquires) {
        if (!hasQueuedPredecessors() &&  // 检查是否有前驱节点
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        // 可重入逻辑...
    }
    

    严格按照FIFO顺序分配锁,避免饥饿现象

锁的获取与释放流程

  1. 加锁过程​:

    • 尝试通过CAS修改state状态
    • 成功则设置当前线程为独占线程
    • 失败则构造Node加入CLH队列尾部,并阻塞线程
  2. 释放过程​:

    • 减少持有计数(state减1)
    • 当state为0时完全释放锁
    • 唤醒队列中的下一个等待线程

ReentrantLock实战应用

生产者-消费者模型

使用ReentrantLock配合Condition实现高效的生产者-消费者模式:

public class BoundedBuffer {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Object[] items = new Object[100];
    private int putPtr, takePtr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();  // 等待"不满"条件
            items[putPtr] = x;
            if (++putPtr == items.length) putPtr = 0;
            ++count;
            notEmpty.signal();  // 通知"不空"条件
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();  // 等待"不空"条件
            Object x = items[takePtr];
            if (++takePtr == items.length) takePtr = 0;
            --count;
            notFull.signal();  // 通知"不满"条件
            return x;
        } finally {
            lock.unlock();
        }
    }
}

这种实现比synchronized+wait/notify更高效,因为可以精准唤醒生产者或消费者线程。

银行转账避免死锁

使用tryLock实现带超时的转账操作,避免死锁:

public boolean transfer(Account from, Account to, int amount, long timeout, TimeUnit unit) {
    long stopTime = System.nanoTime() + unit.toNanos(timeout);
    while (true) {
        if (from.getLock().tryLock()) {
            try {
                if (to.getLock().tryLock()) {
                    try {
                        if (from.getBalance() < amount)
                            throw new InsufficientFundsException();
                        from.withdraw(amount);
                        to.deposit(amount);
                        return true;
                    } finally {
                        to.getLock().unlock();
                    }
                }
            } finally {
                from.getLock().unlock();
            }
        }
        if (System.nanoTime() > stopTime)
            return false;
        Thread.sleep(fixedDelay);
    }
}

通过tryLock和超时机制,有效预防了死锁风险。

可中断的任务执行

使用lockInterruptibly实现可中断的任务执行:

public class InterruptibleTask {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void executeTask() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            // 执行可能长时间运行的任务
            while (!Thread.currentThread().isInterrupted()) {
                // 任务逻辑...
            }
        } finally {
            lock.unlock();
        }
    }
}

这种模式适用于需要支持任务取消的场景。

ReentrantLock与synchronized的对比

特性synchronizedReentrantLock
实现层级JVM内置JDK API实现
锁释放自动必须手动调用unlock()
公平锁支持仅非公平支持公平和非公平策略
可中断获取锁不支持支持(lockInterruptibly)
超时获取锁不支持支持(tryLock with timeout)
条件变量单一等待队列支持多个Condition
锁状态查询有限提供丰富查询方法
性能Java 6+优化复杂场景下表现更好
代码简洁性较低(需手动管理)
适用场景简单同步复杂同步需求

在Java 6及以后版本中,synchronized经过锁升级(偏向锁→轻量级锁→重量级锁)优化,性能与ReentrantLock差距已不明显。因此,简单场景推荐使用synchronized,复杂场景才考虑ReentrantLock。

ReentrantLock最佳实践

  1. 始终在finally块中释放锁​:

    确保锁一定会被释放,避免死锁:

    lock.lock();
    try {
        // 临界区代码
    } finally {
        lock.unlock();
    }
    
  2. 避免嵌套锁​:

    尽量不要在持有一个锁的情况下尝试获取另一个锁,容易导致死锁。

  3. 合理选择锁策略​:

    • 高吞吐场景:非公平锁(默认)
    • 避免饥饿场景:公平锁
  4. 优先使用tryLock​:

    特别是涉及多个锁的操作,使用tryLock可以避免死锁。

  5. 合理使用Condition​:

    替代Object的wait/notify,实现更精准的线程通信。

  6. 性能考量​:

    简单同步场景优先选择synchronized,复杂场景才使用ReentrantLock。

总结

ReentrantLock作为Java并发编程中的重要工具,通过其可重入性、公平性选择、灵活的锁获取方式和条件变量支持,为开发者提供了比synchronized更强大的线程同步能力。理解其基于AQS的实现原理,掌握各种高级特性的使用方法,并遵循最佳实践,可以帮助我们构建更高效、更健壮的并发程序。在实际开发中,应根据具体场景需求,在synchronized和ReentrantLock之间做出合理选择。

到此这篇关于Java ReentrantLock的使用与应用实战的文章就介绍到这了,更多相关Java ReentrantLock内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中高效的对象映射库Orika的用法详解

    Java中高效的对象映射库Orika的用法详解

    Orika是一个高效的Java对象映射库,专门用于在Java应用程序中简化对象之间的转换,下面就跟随小编一起来深入了解下Orika的具体使用吧
    2024-11-11
  • Java8 Stream Collectors收集器使用方法解析

    Java8 Stream Collectors收集器使用方法解析

    这篇文章主要介绍了Java8 Stream Collectors收集器使用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • SpringBoot整合Netty服务端的实现示例

    SpringBoot整合Netty服务端的实现示例

    Netty提供了一套完整的API,用于处理网络IO操作,如TCP和UDP套接字,本文主要介绍了SpringBoot整合Netty服务端的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-07-07
  • Java使用嵌套循环模拟ATM机取款业务操作示例

    Java使用嵌套循环模拟ATM机取款业务操作示例

    这篇文章主要介绍了Java使用嵌套循环模拟ATM机取款业务操作,结合实例形式分析了Java模拟ATM机取款业务操作的相关流程控制、数值判断等操作技巧,需要的朋友可以参考下
    2019-11-11
  • 聊聊Spring data jpa @query使用原生SQl,需要注意的坑

    聊聊Spring data jpa @query使用原生SQl,需要注意的坑

    这篇文章主要介绍了Spring data jpa@query使用原生SQl,需要注意的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java中的ArrayList容量及扩容方式

    Java中的ArrayList容量及扩容方式

    这篇文章主要介绍了Java中的ArrayList容量及扩容方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • druid return行为方法源码示例解析

    druid return行为方法源码示例解析

    这篇文章主要为大家介绍了druid return行为源码示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Java详解swagger2如何配置使用

    Java详解swagger2如何配置使用

    编写和维护接口文档是每个程序员的职责,根据Swagger2可以快速帮助我们编写最新的API接口文档,再也不用担心开会前仍忙于整理各种资料了,间接提升了团队开发的沟通效率
    2022-06-06
  • Java中四种访问控制权限解析(private、default、protected、public)

    Java中四种访问控制权限解析(private、default、protected、public)

    java当中有4种访问修饰限定符privat、default(默认访问权限),protected以及public,本文就详细的介绍一下这四种方法的具体使用,感兴趣的可以了解一下
    2023-05-05
  • Jmail发送邮件工具类分享

    Jmail发送邮件工具类分享

    这篇文章主要为大家分享了Jmail发送邮件工具类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06

最新评论