baeldung 2025-03-26
1. 概述
简而言之,锁(Lock) 是一种比标准 synchronized 代码块更灵活、更强大的线程同步机制。
Lock 接口自 Java 1.5 起引入,位于 java.util.concurrent.locks 包中,提供了丰富的锁操作功能。
在本教程中,我们将探讨 Lock 接口的不同实现及其应用场景。
2. Lock 与 synchronized 块的区别
使用 synchronized 块和 Lock API 之间存在一些关键差异:
- 作用范围不同:
synchronized块必须完全包含在一个方法内;而Lock的lock()和unlock()操作可以分别放在不同的方法中。 - 公平性支持:
synchronized不支持公平性——一旦锁被释放,任何线程都可能获取它,无法指定优先级。而LockAPI 可通过构造函数参数启用公平锁,确保等待时间最长的线程优先获得锁。 - 非阻塞尝试:若线程无法获取
synchronized锁,它将一直阻塞。而Lock提供了tryLock()方法,仅在锁可用且未被其他线程持有时才获取锁,从而减少线程等待时间。 - 可中断性:处于“等待”状态以获取
synchronized块访问权限的线程不能被中断。而LockAPI 提供了lockInterruptibly()方法,允许在等待锁时响应中断,并抛出InterruptedException异常。
3. Lock API
Lock 接口的主要方法如下:
void lock():获取锁。如果锁不可用,当前线程将被阻塞,直到锁被释放。void lockInterruptibly():与lock()类似,但允许在等待锁的过程中响应中断,并抛出InterruptedException。boolean tryLock():非阻塞版本的lock()。立即尝试获取锁,成功则返回true。boolean tryLock(long timeout, TimeUnit timeUnit):与tryLock()类似,但最多等待指定的超时时间后放弃。void unlock():释放锁。
⚠️ 重要提示:已加锁的实例必须始终被解锁,否则可能导致死锁。
推荐使用以下结构来确保锁被正确释放:
Lock lock = ...;
lock.lock();
try {
// 访问共享资源
} finally {
lock.unlock();
}
此外,除了 Lock 接口,还有 ReadWriteLock 接口,它维护一对锁:
- 一个用于只读操作(允许多个线程同时读),
- 一个用于写操作(独占)。
只要没有写操作正在进行,多个线程可以同时持有读锁。
ReadWriteLock 声明了两个方法:
Lock readLock():返回用于读操作的锁。Lock writeLock():返回用于写操作的锁。
4. Lock 的实现类
4.1 ReentrantLock
ReentrantLock 实现了 Lock 接口,提供了与 synchronized 相同的并发性和内存语义,但功能更强大。
基本用法示例:
public class SharedObjectWithLock {
ReentrantLock lock = new ReentrantLock();
int counter = 0;
public void perform() {
lock.lock();
try {
// 临界区
counter++;
} finally {
lock.unlock();
}
}
}
✅ 务必使用
try-finally块包裹lock()和unlock(),防止死锁。
使用 tryLock() 示例:
public void performTryLock() {
boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);
if (isLockAcquired) {
try {
// 临界区
} finally {
lock.unlock();
}
}
// 若未获取到锁,可选择重试或执行其他逻辑
}
在此例中,调用 tryLock() 的线程最多等待 1 秒,若仍未获取锁则放弃。
4.2 ReentrantReadWriteLock
ReentrantReadWriteLock 实现了 ReadWriteLock 接口。
加锁规则:
- 读锁:如果没有线程持有写锁或正在请求写锁,多个线程可同时获取读锁。
- 写锁:只有在没有线程进行读或写操作时,一个线程才能获取写锁。
使用示例:
public class SynchronizedHashMapWithReadWriteLock {
Map<String, String> syncHashMap = new HashMap<>();
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock writeLock = lock.writeLock();
public void put(String key, String value) {
try {
writeLock.lock();
syncHashMap.put(key, value);
} finally {
writeLock.unlock();
}
}
public String remove(String key) {
try {
writeLock.lock();
return syncHashMap.remove(key);
} finally {
writeLock.unlock();
}
}
}
对于读操作,使用读锁:
Lock readLock = lock.readLock();
public String get(String key) {
try {
readLock.lock();
return syncHashMap.get(key);
} finally {
readLock.unlock();
}
}
public boolean containsKey(String key) {
try {
readLock.lock();
return syncHashMap.containsKey(key);
} finally {
readLock.unlock();
}
}
多个读线程可同时访问,只要没有写操作正在进行。
4.3 StampedLock(Java 8 引入)
StampedLock 也支持读写锁,但其加锁方法会返回一个 stamp(戳记),用于后续解锁或验证锁是否仍然有效。
基本用法:
public class StampedLockDemo {
Map<String, String> map = new HashMap<>();
private StampedLock lock = new StampedLock();
public void put(String key, String value) {
long stamp = lock.writeLock();
try {
map.put(key, value);
} finally {
lock.unlockWrite(stamp);
}
}
public String get(String key) throws InterruptedException {
long stamp = lock.readLock();
try {
return map.get(key);
} finally {
lock.unlockRead(stamp);
}
}
}
乐观读锁(Optimistic Reading)
StampedLock 还支持乐观读锁:大多数情况下,读操作无需等待写操作完成,因此不需要完整的读锁。
public String readWithOptimisticLock(String key) {
long stamp = lock.tryOptimisticRead();
String value = map.get(key);
// 验证读期间是否有写操作发生
if (!lock.validate(stamp)) {
// 升级为悲观读锁
stamp = lock.readLock();
try {
return map.get(key);
} finally {
lock.unlockRead(stamp);
}
}
return value;
}
乐观读适用于读多写少、且读操作能容忍短暂不一致的场景,性能更高。
5. 条件变量(Condition)
Condition 类允许线程在执行临界区时等待某个条件成立。
例如:一个读线程获取了共享队列的锁,但队列为空,无法消费数据,此时可等待“非空”条件。
传统 Java 使用 wait()、notify() 和 notifyAll() 实现线程通信,而 Condition 提供了更灵活的机制——可定义多个条件。
示例:带容量限制的栈
public class ReentrantLockWithCondition {
Stack<String> stack = new Stack<>();
int CAPACITY = 5;
ReentrantLock lock = new ReentrantLock();
Condition stackEmptyCondition = lock.newCondition(); // 栈空条件
Condition stackFullCondition = lock.newCondition(); // 栈满条件
public void pushToStack(String item) {
try {
lock.lock();
while (stack.size() == CAPACITY) {
stackFullCondition.await(); // 等待栈不满
}
stack.push(item);
stackEmptyCondition.signalAll(); // 通知消费者栈非空
} finally {
lock.unlock();
}
}
public String popFromStack() {
try {
lock.lock();
while (stack.size() == 0) {
stackEmptyCondition.await(); // 等待栈非空
}
return stack.pop();
} finally {
stackFullCondition.signalAll(); // 通知生产者栈有空间
lock.unlock();
}
}
}
每个
Condition对象都与一个Lock关联,通过lock.newCondition()创建。
6. 总结
本文介绍了 java.util.concurrent.locks 包中 Lock 接口的多种实现:
ReentrantLock:基础可重入锁,功能强于synchronized。ReentrantReadWriteLock:适用于读多写少的场景,提升并发性能。StampedLock:Java 8 新增,支持乐观读,进一步优化读密集型应用。
此外,我们还探讨了如何使用 Condition 实现多条件的线程协作,替代传统的 wait/notify 机制,使线程通信更清晰、灵活。
合理使用这些工具,可显著提升多线程程序的性能与可控性。