作为Java并发编程中的重量级选手,ReentrantLock远比表面看起来要复杂得多。记得我第一次在生产环境使用ReentrantLock时,就因为没有完全理解其底层机制而踩过坑。今天我将结合多年实战经验,带你彻底掌握这把锁的方方面面。
ReentrantLock是Java并发包(java.util.concurrent.locks)中提供的显式锁实现,它解决了synchronized关键字的几个关键痛点。与synchronized不同,ReentrantLock提供了:
在实际项目中,当我们需要这些高级特性时,ReentrantLock就成为了不二之选。特别是在分布式锁、任务调度等场景中,它的灵活性表现得淋漓尽致。
ReentrantLock的核心是基于AQS(AbstractQueuedSynchronizer)实现的。AQS使用一个volatile int类型的state变量来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
这种设计有几个精妙之处:
理解这些底层机制,对我们正确使用ReentrantLock至关重要。比如,知道非公平锁的实现原理,就能明白为什么它在高并发场景下吞吐量更高。
可重入性是ReentrantLock的基础特性,它允许同一个线程多次获取同一把锁。这个特性是通过记录当前持有锁的线程和重入次数实现的。
java复制final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
从源码可以看出,当线程尝试获取锁时,会先检查state是否为0。如果不为0,则检查当前线程是否是锁的持有者。如果是,则简单增加重入次数。
重要提示:每次lock()操作都必须有对应的unlock()操作,否则会导致锁无法释放。这就是为什么我们要把unlock()放在finally块中。
公平性选择是ReentrantLock的一个重要特性。让我们通过一个实际案例来看看它们的区别:
java复制// 非公平锁测试
ReentrantLock unfairLock = new ReentrantLock();
// 公平锁测试
ReentrantLock fairLock = new ReentrantLock(true);
void testLock(ReentrantLock lock) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 2; j++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获取锁");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
}
}
在实际测试中,你会发现:
性能对比:
选择建议:
中断响应是ReentrantLock区别于synchronized的重要特性。考虑以下场景:当线程在等待锁时,如果收到中断信号,能够立即响应而不是一直阻塞。
java复制public void performTaskWithTimeout() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
try {
// 尝试在2秒内获取锁
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
// 执行关键代码
System.out.println("锁获取成功,执行任务");
Thread.sleep(1000); // 模拟任务执行
} finally {
lock.unlock();
}
} else {
System.out.println("获取锁超时");
}
} catch (InterruptedException e) {
System.out.println("线程被中断");
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
中断机制的正确使用需要注意:
超时机制在实际系统中非常有用,它可以防止死锁和系统假死。下面是一个数据库连接管理的例子:
java复制public class ConnectionPool {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final List<Connection> pool = new ArrayList<>();
private final int maxSize;
public ConnectionPool(int maxSize) {
this.maxSize = maxSize;
// 初始化连接池
}
public Connection getConnection(long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException {
long nanos = unit.toNanos(timeout);
lock.lockInterruptibly();
try {
while (pool.isEmpty()) {
if (nanos <= 0)
throw new TimeoutException();
nanos = notEmpty.awaitNanos(nanos);
}
return pool.remove(0);
} finally {
lock.unlock();
}
}
}
在这个实现中:
ReentrantLock的条件变量比Object的wait/notify更灵活,它可以创建多个等待队列。典型的生产者-消费者模式实现:
java复制public class BoundedBuffer {
final ReentrantLock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
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();
}
}
}
使用条件变量的要点:
虽然ReentrantLock支持可重入,但过度嵌套会导致代码难以维护。这里有个重构的例子:
java复制// 重构前
public void processOrder(Order order) {
lock.lock();
try {
validate(order);
lock.lock(); // 冗余的锁获取
try {
updateInventory(order);
lock.lock();
try {
sendNotification(order);
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
// 重构后
public void processOrder(Order order) {
lock.lock();
try {
validate(order);
updateInventory(order);
sendNotification(order);
} finally {
lock.unlock();
}
}
重构建议:
在高并发场景下,ReentrantLock的性能调优很重要:
java复制// 优化前
lock.lock();
try {
result = compute(); // 耗时计算
write(result);
} finally {
lock.unlock();
}
// 优化后
result = compute(); // 移到锁外
lock.lock();
try {
write(result);
} finally {
lock.unlock();
}
java复制class StripedMap {
private final ReentrantLock[] locks;
private final Map<String, Object>[] segments;
public StripedMap(int stripes) {
locks = new ReentrantLock[stripes];
for (int i = 0; i < stripes; i++)
locks[i] = new ReentrantLock();
segments = new Map[stripes];
for (int i = 0; i < stripes; i++)
segments[i] = new HashMap<>();
}
private int hash(Object key) {
return Math.abs(key.hashCode() % locks.length);
}
public void put(String key, Object value) {
int hash = hash(key);
locks[hash].lock();
try {
segments[hash].put(key, value);
} finally {
locks[hash].unlock();
}
}
}
java复制class CachedData {
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
final Lock readLock = rwl.readLock();
final Lock writeLock = rwl.writeLock();
void processCachedData() {
readLock.lock();
try {
if (!cacheValid) {
// 必须在释放读锁前获取写锁
readLock.unlock();
writeLock.lock();
try {
// 重新检查状态,因为可能有其他线程已经获取了写锁
if (!cacheValid) {
updateCache();
cacheValid = true;
}
// 降级为读锁
readLock.lock();
} finally {
writeLock.unlock(); // 释放写锁,保持读锁
}
}
use(cachedData);
} finally {
readLock.unlock();
}
}
}
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 实现层次 | Java代码层面 | JVM层面 |
| 锁获取方式 | 显式调用lock()/unlock() | 隐式获取和释放 |
| 可中断性 | 支持 | 不支持 |
| 公平性 | 可配置 | 非公平 |
| 性能 | 略优于synchronized(Java 6+) | 优化后差距不大 |
| 条件变量 | 支持多个Condition | 单个等待队列 |
| 锁释放 | 必须显式释放 | 自动释放 |
选择ReentrantLock当:
选择synchronized当:
在Java 8环境下,对两种锁进行基准测试(纳秒/操作):
| 线程数 | ReentrantLock(非公平) | synchronized |
|---|---|---|
| 1 | 45 | 42 |
| 2 | 78 | 85 |
| 4 | 132 | 150 |
| 8 | 245 | 320 |
| 16 | 480 | 620 |
测试结果表明:
使用ReentrantLock时,死锁风险依然存在。下面是一个检测死锁的工具类:
java复制public class DeadlockDetector {
private static final ThreadMXBean bean = ManagementFactory.getThreadMXBean();
public static void detect() {
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
ThreadInfo[] infos = bean.getThreadInfo(threadIds);
for (ThreadInfo info : infos) {
System.err.println("死锁检测到: " + info.getThreadName());
StackTraceElement[] stack = info.getStackTrace();
for (StackTraceElement ste : stack) {
System.err.println("\t" + ste);
}
}
// 可以选择中断线程或报警
}
}
// 定时检测
public static void startDetection(long period) {
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
DeadlockDetector::detect, period, period, TimeUnit.SECONDS);
}
}
预防死锁的建议:
锁泄漏是指锁没有被正确释放的情况。我们可以扩展ReentrantLock来跟踪锁的状态:
java复制public class TrackingReentrantLock extends ReentrantLock {
private transient Thread owner;
@Override
public void lock() {
super.lock();
owner = Thread.currentThread();
}
@Override
public void unlock() {
super.unlock();
if (!isHeldByCurrentThread()) {
owner = null;
}
}
public boolean isLockLeaked() {
return isLocked() && owner != null && !owner.isAlive();
}
public String getOwnerStack() {
if (owner == null) return null;
StringBuilder sb = new StringBuilder();
for (StackTraceElement ste : owner.getStackTrace()) {
sb.append("\tat ").append(ste).append("\n");
}
return sb.toString();
}
}
使用这个工具可以:
当系统出现性能问题时,如何判断是否与ReentrantLock有关?
code复制"Thread-1" #12 prio=5 os_prio=0 tid=0x00007f8a3c0b8000 nid=0x1e03 waiting on condition [0x00007f8a2a7f6000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b84c1b8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
经过多年实践,我总结了以下ReentrantLock使用原则:
记住,ReentrantLock是强大的工具,但能力越大责任越大。只有深入理解其原理,才能在复杂系统中游刃有余地使用它。