在Java并发编程中,ReentrantLock
是 java.util.concurrent.locks
包下的一个重要类,它提供了一种比传统 synchronized
方法和语句更灵活的锁机制。ReentrantLock
实现了 Lock
接口,提供了与 synchronized
相似的互斥性,但更加灵活和强大。它不仅支持公平锁和非公平锁,还提供了尝试非阻塞地获取锁、尝试可中断地获取锁以及锁定时的定时等待等功能。接下来,我们将深入探讨 ReentrantLock
的使用方法和一些高级特性。
一、ReentrantLock 的基本使用
1. 创建 ReentrantLock 实例
首先,你需要创建一个 ReentrantLock
的实例。这个实例代表了锁本身,你可以通过它来加锁和解锁。
ReentrantLock lock = new ReentrantLock();
2. 加锁与解锁
使用 ReentrantLock
时,你需要在代码块的前后分别调用 lock()
和 unlock()
方法来确保锁的正确获取和释放。
lock.lock();
try {
// 访问或修改被锁保护的共享资源
} finally {
lock.unlock();
}
注意,unlock()
方法应该放在 finally
块中,以确保即使在发生异常的情况下,锁也能被正确释放,从而避免死锁。
3. 尝试非阻塞地获取锁
如果你不希望当前线程在无法立即获取锁时阻塞,可以使用 tryLock()
方法。这个方法会立即返回一个布尔值,指示是否成功获取了锁。
if (lock.tryLock()) {
try {
// 访问或修改被锁保护的共享资源
} finally {
lock.unlock();
}
} else {
// 无法获取锁,执行其他逻辑
}
4. 尝试可中断地获取锁
ReentrantLock
还提供了 tryLock(long time, TimeUnit unit)
方法,允许线程在等待获取锁的过程中被中断。如果在指定的等待时间内线程获得了锁,则返回 true
;如果线程在等待过程中被中断,则返回 false
。
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 访问或修改被锁保护的共享资源
} finally {
lock.unlock();
}
} else {
// 无法在指定时间内获取锁
}
} catch (InterruptedException e) {
// 当前线程在等待过程中被中断
Thread.currentThread().interrupt(); // 保持中断状态
}
二、ReentrantLock 的高级特性
1. 公平锁与非公平锁
ReentrantLock
支持公平锁和非公平锁两种模式。默认情况下,ReentrantLock
采用非公平锁模式,这意味着锁会偏向那些已经持有锁的线程或者能够立即获取锁的线程,从而可能导致“饥饿”现象,即某些线程长时间无法获取锁。
公平锁则严格按照线程请求锁的顺序来获取锁,这有助于减少饥饿现象,但可能会降低吞吐量。
你可以通过构造函数来指定使用哪种锁模式:
// 创建一个公平锁
ReentrantLock fairLock = new ReentrantLock(true);
// 创建一个非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock(false);
2. 锁的可重入性
ReentrantLock
是一个可重入的锁,这意味着同一个线程可以多次获得已经持有的锁。每次调用 lock()
方法都会增加锁的持有计数,每次调用 unlock()
方法都会减少计数,直到计数为0时,锁才被完全释放。
这种特性允许线程在持有锁的情况下,再次进入由该锁保护的同步块,这在递归调用时非常有用。
3. 锁的条件(Condition)
ReentrantLock
还支持与 synchronized
完全不同的等待/通知机制,即 Condition
接口。一个 ReentrantLock
可以有多个 Condition
实例,每个 Condition
实例都独立地管理那些等待获取锁的线程。
使用 Condition
可以更精细地控制线程间的协作,比如实现复杂的同步控制逻辑,如生产者-消费者问题等。
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
// 等待条件
condition.await();
// 修改条件
// ...
// 通知其他线程
condition.signal();
} finally {
lock.unlock();
}
三、ReentrantLock 与 synchronized 的比较
虽然 ReentrantLock
和 synchronized
都能实现同步,但它们在灵活性、功能、性能等方面存在显著差异。
- 灵活性:
ReentrantLock
提供了比synchronized
更丰富的功能,如尝试非阻塞地获取锁、尝试可中断地获取锁、超时等待锁以及支持公平锁等。 - 功能:
ReentrantLock
支持多个条件变量(Condition
),而synchronized
关键字只支持一个隐式的条件队列。 - 性能:在竞争不激烈的情况下,
synchronized
的性能可能优于ReentrantLock
,因为synchronized
是JVM层面的锁,其优化空间更大。但在高竞争场景下,ReentrantLock
的性能可能更优,因为其提供了更多的控制选项。
四、使用场景
- 需要公平锁时:如果应用程序中需要确保锁的获取顺序与请求顺序一致,那么应该使用
ReentrantLock
的公平锁模式。 - 需要中断响应时:如果线程在等待锁的过程中需要响应中断,那么应该使用
ReentrantLock
的可中断获取锁功能。 - 需要多个条件变量时:如果同步块中需要根据不同条件唤醒不同线程,那么应该使用
ReentrantLock
及其Condition
。 - 复杂同步控制:在需要实现复杂同步控制逻辑时,如生产者-消费者问题、读写锁等,
ReentrantLock
提供了更灵活的控制手段。
五、总结
ReentrantLock
是Java并发编程中一个非常重要的工具,它提供了比 synchronized
更灵活、更强大的锁机制。通过合理使用 ReentrantLock
,我们可以更好地控制线程间的同步和协作,从而提高程序的并发性能和可靠性。在码小课(我的网站)上,你可以找到更多关于Java并发编程的深入解析和实战案例,帮助你更好地掌握这些高级特性。