ReentrantLock 是 Java 中一个强大的可重入锁实现,位于 java.util.concurrent.locks 包下,提供了比 synchronized 更灵活、更高级的锁控制能力。以下是对其核心特性、原理及优雅使用方式的详细解析:
一、核心特性
-
可重入性
同一线程可多次获取同一把锁而不会被阻塞,需通过相同次数的unlock()释放锁。 -
公平/非公平锁
- 公平锁:按请求顺序获取锁(吞吐量较低)。
- 非公平锁:允许插队,性能更高(默认模式)。
-
可中断锁
通过lockInterruptibly()方法获取锁时,线程可被中断。 -
超时锁
通过tryLock(long timeout, TimeUnit unit)尝试获取锁,超时可放弃。 -
条件变量(Condition)
支持多个等待队列,比Object.wait()更灵活。
二、与 synchronized 的对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁获取方式 | 隐式(基于代码块) | 显式调用 lock() 和 unlock() |
| 可重入性 | 支持 | 支持 |
| 公平性 | 不支持 | 支持(可配置) |
| 可中断性 | 不支持 | 支持 |
| 超时机制 | 不支持 | 支持 |
| 条件变量 | 单一 wait/notify 队列 | 多个 Condition 队列 |
| 锁状态查询 | 无法获取 | 可通过 isLocked() 查询 |
三、优雅使用 ReentrantLock 的场景与示例
1. 基础用法:替代 synchronized
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
count++; // 临界区
} finally {
lock.unlock(); // 释放锁(必须在finally中执行)
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
2. 公平锁示例
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private final ReentrantLock lock = new ReentrantLock(true); // 公平锁
public void doWork() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取锁");
// 模拟耗时操作
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
FairLockExample example = new FairLockExample();
// 启动多个线程,观察获取锁的顺序
for (int i = 0; i < 5; i++) {
new Thread(example::doWork, "Thread-" + i).start();
}
}
}
3. 可中断锁示例
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void task() throws InterruptedException {
lock.lockInterruptibly(); // 可中断的锁获取
try {
System.out.println(Thread.currentThread().getName() + " 执行任务");
Thread.sleep(5000);
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
InterruptibleLockExample example = new InterruptibleLockExample();
Thread t1 = new Thread(() -> {
try {
example.task();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 被中断");
}
}, "T1");
Thread t2 = new Thread(() -> {
try {
example.task();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 被中断");
}
}, "T2");
t1.start();
Thread.sleep(100); // 确保T1先获取锁
t2.start();
Thread.sleep(1000); // 1秒后中断T2
t2.interrupt();
}
}
4. 超时锁示例
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutLockExample {
private final ReentrantLock lock = new ReentrantLock();
public boolean tryCriticalSection(long timeout, TimeUnit unit) throws InterruptedException {
if (lock.tryLock(timeout, unit)) { // 尝试获取锁,超时返回false
try {
System.out.println(Thread.currentThread().getName() + " 获取锁成功");
// 执行临界区代码
return true;
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 获取锁超时");
return false;
}
}
}
5. 条件变量(Condition)示例
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedQueue<T> {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 队列未满条件
private final Condition notEmpty = lock.newCondition(); // 队列非空条件
private final T[] queue;
private int head, tail, count;
@SuppressWarnings("unchecked")
public BoundedQueue(int capacity) {
this.queue = (T[]) new Object[capacity];
}
public void enqueue(T item) throws InterruptedException {
lock.lock();
try {
while (count == queue.length) {
notFull.await(); // 队列已满,等待
}
queue[tail] = item;
tail = (tail + 1) % queue.length;
count++;
notEmpty.signal(); // 通知可能等待的消费者
} finally {
lock.unlock();
}
}
public T dequeue() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 队列已空,等待
}
T item = queue[head];
head = (head + 1) % queue.length;
count--;
notFull.signal(); // 通知可能等待的生产者
return item;
} finally {
lock.unlock();
}
}
}
四、最佳实践与注意事项
-
必须在
finally块中释放锁
确保锁无论如何都会被释放,避免死锁。 -
优先使用 try-with-resources(Java 8+)
将锁封装在 AutoCloseable 类中,实现自动释放:public class AutoLock implements AutoCloseable { private final ReentrantLock lock; public AutoLock(ReentrantLock lock) { this.lock = lock; this.lock.lock(); } @Override public void close() { lock.unlock(); } } // 使用示例 public void safeLock() { try (AutoLock al = new AutoLock(lock)) { // 临界区 } // 自动释放锁 } -
避免锁嵌套
嵌套锁可能导致死锁,确保锁的获取和释放顺序一致。 -
合理选择公平锁/非公平锁
公平锁适用于需要严格顺序的场景,非公平锁适用于高性能场景。 -
锁的粒度控制
避免锁范围过大,影响并发性能。
五、源码与原理(简要)
ReentrantLock 基于 AQS(AbstractQueuedSynchronizer)实现:
- 公平锁:通过
hasQueuedPredecessors()方法确保先到先得。 - 非公平锁:允许线程在队列前直接尝试获取锁。
- 可重入性:通过
getState()记录锁的持有次数,支持重入计数。
六、总结
ReentrantLock 是 Java 中强大的锁工具,适合需要更灵活控制的并发场景,如:
- 需要可中断锁或超时机制。
- 需要多个等待队列(Condition)。
- 需要公平锁特性。
- 需要精确控制锁的获取和释放时机。
在使用时,务必遵循“获取锁后立即 try,释放锁放在 finally”的原则,确保代码健壮性。



被折叠的 条评论
为什么被折叠?



