java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java ReentrantLock

Java ReentrantLock的使用与应用实战

作者:IT橘子皮

ReentrantLock是Java并发包中提供的一种可重入互斥锁,它作为synchronized关键字的替代方案,提供了更灵活、更强大的线程同步机制,下面就来介绍一下ReentrantLock的实战使用

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

ReentrantLock概述与基本特性

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

从实现层级看,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提供了一系列状态查询方法:

ReentrantLock实现原理

AQS框架基础

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

公平锁与非公平锁实现

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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文