Java并发编程中的读写锁机制解析
读写锁的核心原理
在高并发环境下,共享资源的访问控制至关重要。传统的独占锁(如synchronized)虽然简单,但在读多写少的场景下效率低下。读写锁(ReadWriteLock)通过分离读操作与写操作的锁机制,允许多个线程同时读取数据,仅在写操作时进行排他访问,从而大幅提升系统吞吐量。
接口设计与实现
ReadWriteLock接口定义了两个核心方法:
readLock():获取用于读取操作的锁,支持并发读取。writeLock():获取用于写入操作的锁,保证独占访问。
其典型实现为ReentrantReadWriteLock,具备重入特性,即同一线程可多次获取相同类型的锁,避免死锁风险。
锁行为规则
- 读锁共享:当无写锁持有时,多个线程可同时获取读锁。
- 写锁独占:一旦有线程持有写锁,其他所有读写请求均被阻塞。
- 写优先:通常情况下,写锁具有更高优先级,防止写操作长期等待。
实际应用示例
以下代码演示如何使用读写锁保护一个共享计数器:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class DataAccessor {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final var readLock = lock.readLock();
private final var writeLock = lock.writeLock();
private int data = 0;
public void displayValue() {
readLock.lock();
try {
System.out.println("Current value: " + data);
} finally {
readLock.unlock();
}
}
public void updateValue(int newValue) {
writeLock.lock();
try {
data = newValue;
System.out.println("Updated to: " + data);
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
var accessor = new DataAccessor();
// 启动多个读线程
var reader1 = new Thread(accessor::displayValue);
var reader2 = new Thread(accessor::displayValue);
// 启动写线程
var writer = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
accessor.updateValue(i * 100);
}
});
reader1.start();
reader2.start();
writer.start();
try {
reader1.join();
reader2.join();
writer.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
锁升级与降级策略
在某些逻辑中,可能需要先读取数据再决定是否修改。此时可实现"锁升级":
- 线程先获取读锁,检查条件。
- 若需更新,则释放读锁并获取写锁。
同样,也可实现"锁降级":先持写锁,完成写入后降级为读锁以供后续读取。
公平性配置
ReentrantReadWriteLock支持两种模式:
- 非公平模式(默认):允许插队,提升吞吐量。
- 公平模式:按请求顺序分配锁,避免饥饿。
可通过构造函数指定:
private final ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true); // 公平锁
注意事项
- 避免在持有读锁期间调用可能阻塞的方法,防止死锁。
- 锁升级必须在不持有读锁的情况下进行,否则可能导致死锁。
- 合理评估锁竞争程度,避免过度使用读写锁带来的开销。