Java 可重入锁 (ReentrantLock) 详解
在 Java 中,可重入锁 (ReentrantLock) 是 java.util.concurrent.locks
包中的一个类,它实现了 Lock
接口。可重入锁为多线程环境中的线程同步提供了更灵活的锁机制,通常用于替代 synchronized
关键字。
1. 什么是可重入锁?
可重入锁(ReentrantLock) 是一种允许线程在持有锁的情况下多次获取同一个锁的机制。换句话说,如果一个线程已经持有了锁,并在同一个线程中再次请求该锁,则该线程将被允许继续持有锁,而不会被阻塞。这种行为称为 锁的重入性。
示例解释:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void methodA() {
lock.lock(); // 获取锁
try {
System.out.println("Method A is executing.");
methodB(); // 在持有锁的情况下调用另一个方法
} finally {
lock.unlock(); // 释放锁
}
}
public void methodB() {
lock.lock(); // 再次获取同一个锁
try {
System.out.println("Method B is executing.");
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
example.methodA();
}
}
输出:
Method A is executing.
Method B is executing.
在上面的例子中,methodA
和 methodB
都使用了同一个锁对象。由于 methodA
在持有锁时调用了 methodB
,但由于 ReentrantLock 支持重入,methodB
可以正常执行。
2. 可重入锁的核心特性
特性 | 描述 |
---|---|
重入性 (Reentrancy) | 同一线程可以多次获取同一把锁,而不会被阻塞。 |
公平锁与非公平锁 | 可重入锁支持公平和非公平锁(默认是非公平锁)。 |
锁中断支持 | 支持线程在等待锁时的中断响应。 |
条件变量支持 (Condition) | 支持多个条件变量,可以更灵活地实现等待/通知机制。 |
3. 可重入锁的主要方法
方法 | 描述 |
---|---|
lock() | 获取锁,如果锁不可用,则等待。 |
lockInterruptibly() | 获取锁,支持中断等待过程。 |
tryLock() | 尝试获取锁,成功返回 true ,失败返回 false 。 |
tryLock(long time, TimeUnit unit) | 尝试在指定时间内获取锁,超时失败。 |
unlock() | 释放锁。 |
isHeldByCurrentThread() | 检查当前线程是否持有该锁。 |
getHoldCount() | 返回当前线程持有该锁的次数(重入次数)。 |
isLocked() | 检查锁是否被任意线程持有。 |
4. 可重入锁的公平性和非公平性
-
非公平锁(默认):
- 在争夺锁时,线程的调度不遵循严格的先来先服务规则。
- 性能更高,适用于高吞吐量场景。
- 默认构造方法:
ReentrantLock lock = new ReentrantLock(); // 非公平锁
-
公平锁(需要手动设置):
- 锁的获取遵循先来先服务(FIFO)规则。
- 开销更大,性能稍低。
- 设置公平锁:
ReentrantLock lock = new ReentrantLock(true); // 公平锁
5. 可重入锁的应用示例
示例 1:使用 tryLock()
避免死锁
import java.util.concurrent.locks.ReentrantLock;
public class TryLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void tryLockExample() {
if (lock.tryLock()) {
try {
System.out.println("Lock acquired, executing critical section.");
} finally {
lock.unlock();
}
} else {
System.out.println("Failed to acquire lock.");
}
}
public static void main(String[] args) {
TryLockExample example = new TryLockExample();
example.tryLockExample();
}
}
示例 2:实现生产者-消费者模型
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumer {
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition notEmpty = lock.newCondition();
private static final Condition notFull = lock.newCondition();
private static int count = 0;
public static void main(String[] args) {
Thread producer = new Thread(() -> {
while (true) {
lock.lock();
try {
while (count >= 10) {
notFull.await(); // 阻塞,直到 notFull 条件满足
}
count++;
System.out.println("Produced: " + count);
notEmpty.signal(); // 通知消费者可以消费
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
Thread consumer = new Thread(() -> {
while (true) {
lock.lock();
try {
while (count <= 0) {
notEmpty.await(); // 阻塞,直到 notEmpty 条件满足
}
System.out.println("Consumed: " + count);
count--;
notFull.signal(); // 通知生产者可以生产
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
producer.start();
consumer.start();
}
}
6. 使用注意事项
- 手动释放锁:使用
lock()
时,需要在finally
块中调用unlock()
,以确保锁能够正常释放,避免死锁。 - 公平与性能权衡:公平锁适用于需要严格遵循 FIFO 的场景,但性能较低,非公平锁性能更高。
- 尽量缩小锁的范围:将锁的作用域尽量缩小,以减少锁的持有时间,提高程序性能。
- 条件变量的使用:
Condition
对象应与ReentrantLock
配合使用,以实现更加灵活的线程间通信。
总结
- ReentrantLock(可重入锁) 是 Java 提供的一个强大的线程同步机制。
- 它提供了比
synchronized
关键字更灵活的功能,如公平锁、可中断锁、尝试获取锁等。 - 在多线程编程中,了解如何正确使用 ReentrantLock 是编写高性能和可靠并发程序的关键。