解释重入锁(ReentrantLock)的概念及其工作原理。
参考回答
重入锁(ReentrantLock) 是 Java 提供的一种可重入的锁,位于 java.util.concurrent.locks
包中。它具有类似于 synchronized
的锁功能,但提供了更灵活的锁机制,支持显式锁获取和释放,以及可中断、超时、和公平锁等特性。
关键点:
- 可重入:同一个线程可以多次获得同一把锁,而不会导致死锁。
- 显式控制:需要显式调用
lock()
和unlock()
来加锁和解锁。 - 灵活性:支持公平锁和非公平锁、锁中断、超时等高级特性。
详细讲解与拓展
1. ReentrantLock 的主要特性
特性 | 描述 |
---|---|
可重入 | 同一线程可以重复获得锁,但每次 lock() 都必须对应一次 unlock() 。 |
公平锁和非公平锁 | 默认是非公平锁,锁的分配顺序可能与线程的等待顺序无关;也可以设置为公平锁,按线程等待顺序获取锁。 |
中断支持 | 线程在等待锁时,可以响应中断,通过 lockInterruptibly() 方法实现。 |
超时获取锁 | 通过 tryLock(long timeout, TimeUnit unit) 可以在超时时间内尝试获取锁,避免死锁。 |
条件变量支持 | 提供 Condition 对象,用于实现复杂的线程间通信(如 await() 和 signal() )。 |
2. 使用示例
基本用法:加锁与解锁
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void criticalSection() {
lock.lock(); // 加锁
try {
// 临界区代码
System.out.println(Thread.currentThread().getName() + " is executing critical section");
} finally {
lock.unlock(); // 确保释放锁
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Runnable task = () -> {
for (int i = 0; i < 3; i++) {
example.criticalSection();
}
};
Thread t1 = new Thread(task, "Thread-1");
Thread t2 = new Thread(task, "Thread-2");
t1.start();
t2.start();
}
}
输出示例:
Thread-1 is executing critical section
Thread-2 is executing critical section
Thread-1 is executing critical section
Thread-2 is executing critical section
...
关键点:
- 必须显式调用
lock()
和unlock()
,确保锁的正确释放(使用finally
块)。 - 如果忘记释放锁,可能导致死锁。
公平锁与非公平锁
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private final ReentrantLock lock = new ReentrantLock(true); // 创建公平锁
public void criticalSection() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " got the lock");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
FairLockExample example = new FairLockExample();
Runnable task = example::criticalSection;
for (int i = 1; i <= 5; i++) {
new Thread(task, "Thread-" + i).start();
}
}
}
输出示例:
Thread-1 got the lock
Thread-2 got the lock
Thread-3 got the lock
Thread-4 got the lock
Thread-5 got the lock
解释:
- 公平锁按照线程的等待顺序分配锁,避免“饥饿”现象。
- 非公平锁性能更高,因为线程可能会“抢占”锁,减少线程调度的开销。
支持中断的锁
public class InterruptibleLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void interruptibleTask() {
try {
lock.lockInterruptibly(); // 支持中断的加锁方式
System.out.println(Thread.currentThread().getName() + " acquired the lock");
Thread.sleep(2000); // 模拟任务
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock(); // 确保释放锁
}
}
}
public static void main(String[] args) {
InterruptibleLockExample example = new InterruptibleLockExample();
Thread t1 = new Thread(example::interruptibleTask, "Thread-1");
Thread t2 = new Thread(() -> {
example.interruptibleTask();
}, "Thread-2");
t1.start();
t2.start();
try {
Thread.sleep(500); // 等待一段时间后中断线程 2
t2.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出示例:
Thread-1 acquired the lock
Thread-2 was interrupted
3. ReentrantLock 的工作原理
- 底层基于 AQS(AbstractQueuedSynchronizer)
ReentrantLock
使用 AQS 实现,其核心是一个双向队列和状态变量。-
state变量记录锁的状态:
- 0 表示锁未被占用。
- 大于 0 表示锁被占用,值代表重入次数。
- 获取锁(lock)
- 非公平锁:线程尝试直接 CAS 修改
state
为 1,如果成功则获得锁。 - 公平锁:线程需要先检查队列中是否有其他线程等待锁,避免抢占。
- 释放锁(unlock)
- 释放锁时,
state
减 1,如果减到 0,锁完全释放,并唤醒等待队列中的线程。
- 支持可重入
- 同一线程再次调用
lock()
,只需增加state
值,而不需要阻塞。
4. 使用场景
- 高并发场景:需要显式控制锁的获取和释放。
- 复杂线程间同步:需要使用
Condition
实现复杂的等待和通知机制。 - 避免死锁:通过超时和中断机制降低死锁风险。
5. 小结
- ReentrantLock 的优势:
- 比
synchronized
更灵活(如支持公平锁、中断、超时等)。 - 提供显式锁控制,适合复杂并发场景。
- 比
- ReentrantLock 的缺点:
- 显式调用
lock()
和unlock()
,容易出现忘记释放锁的问题。 - 在简单场景下,
synchronized
更适合,因为它会自动管理锁。
- 显式调用