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 中 Reference用法详解

    Java 中 Reference用法详解

    这篇文章主要介绍了Java 中 Reference用法详解的相关资料,需要的朋友可以参考下
    2017-03-03
  • MyBatis处理懒加载和预加载的方式

    MyBatis处理懒加载和预加载的方式

    在 MyBatis 中,懒加载(Lazy Loading)和预加载(Eager Loading)是两种不同的查询策略,用于控制关联对象的加载时机和方式,本文给大家详细介绍了具体处理方式,需要的朋友可以参考下
    2025-11-11
  • springboot3+r2dbc响应式编程实践

    springboot3+r2dbc响应式编程实践

    本文主要介绍了springboot3+r2dbc响应式编程实践,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • SpringBoot如何获取图片的宽高

    SpringBoot如何获取图片的宽高

    本文介绍了Java中获取图片宽高尺寸的三种方法:使用BufferedImage、Image类和更底层的字节流方式,BufferedImage和Image类适用于大多数情况,而字节流方式则适用于特定格式的高性能场景,感兴趣的朋友跟随小编一起看看吧
    2025-11-11
  • 解决日期转化Json异常- Date JSON parse error

    解决日期转化Json异常- Date JSON parse error

    这篇文章主要介绍了解决日期转化Json异常- Date JSON parse error问题。具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • java脚本使用不同版本jdk的说明介绍

    java脚本使用不同版本jdk的说明介绍

    本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用于JDK8及以下版本,而Nashorn和GraalVM分别推荐在JDK8及以上和JDK11及以上使用,后者支持更多语言并性能更优
    2025-01-01
  • SpringAop切入点execution表达式的深入讲解

    SpringAop切入点execution表达式的深入讲解

    Spring AOP 可能会经常使用 execution切入点指示符,下面这篇文章主要给大家介绍了关于SpringAop切入点execution表达式的相关资料,需要的朋友可以参考下
    2021-08-08
  • 一篇文章带你了解java Object根类中关于toString,equals的方法

    一篇文章带你了解java Object根类中关于toString,equals的方法

    这篇文章主要介绍了Object类toString()和equals()方法使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-09-09
  • SpringSecurity实现登陆认证并返回token方式

    SpringSecurity实现登陆认证并返回token方式

    这篇文章主要介绍了SpringSecurity实现登陆认证并返回token方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • 基于String和List<String>间的相互转换方式

    基于String和List<String>间的相互转换方式

    这篇文章主要介绍了基于String和List间的相互转换方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05

最新评论