目录
ReentrantReadWriteLock
是 Java 并发包中提供的一种读写锁实现,它允许多个线程同时读取共享资源,但在写入时只允许一个线程独占访问。
基本概念
读写锁的特点
-
读-读不互斥:多个线程可以同时获取读锁
-
读-写互斥:读锁和写锁不能同时持有
-
写-写互斥:同一时间只能有一个线程持有写锁
与普通互斥锁的区别
-
普通互斥锁(如
ReentrantLock
)在任何时候都只允许一个线程访问 -
读写锁在读多写少的场景下能显著提高并发性能
核心结构
ReentrantReadWriteLock
由两部分组成:
-
读锁(
ReadLock
) -
写锁(
WriteLock
)
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
主要特性
1. 可重入性
与 ReentrantLock
类似,读写锁也是可重入的:
-
线程可以重复获取已经持有的读锁
-
线程可以重复获取已经持有的写锁
-
持有写锁的线程可以获取读锁(锁降级)
-
但持有读锁的线程不能直接获取写锁(可能导致死锁)
2. 公平性选择
构造函数可以指定公平策略:
// 非公平锁(默认)
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 公平锁
ReentrantReadWriteLock fairRwLock = new ReentrantReadWriteLock(true);
公平锁保证等待时间最长的线程优先获取锁,但会降低吞吐量。
3. 锁降级
允许从写锁降级为读锁:
writeLock.lock();
try {
// 写操作...
readLock.lock(); // 锁降级
} finally {
writeLock.unlock();
}
// 此时仍持有读锁
try {
// 读操作...
} finally {
readLock.unlock();
}
4. 不支持锁升级
不允许从读锁升级为写锁,尝试这样做会导致死锁:
readLock.lock();
try {
writeLock.lock(); // 这里会阻塞,导致死锁
try {
// ...
} finally {
writeLock.unlock();
}
} finally {
readLock.unlock();
}
使用示例
基本用法
public class Cache {
private final Map<String, Object> map = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public Object get(String key) {
rwLock.readLock().lock();
try {
return map.get(key);
} finally {
rwLock.readLock().unlock();
}
}
public void put(String key, Object value) {
rwLock.writeLock().lock();
try {
map.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}
锁降级示例
public void processData() {
rwLock.readLock().lock();
if (!cacheValid) {
// 必须先释放读锁才能获取写锁
rwLock.readLock().unlock();
rwLock.writeLock().lock();
try {
// 再次检查,因为可能有其他线程已经修改了
if (!cacheValid) {
// 更新数据...
cacheValid = true;
}
// 锁降级:在释放写锁前获取读锁
rwLock.readLock().lock();
} finally {
rwLock.writeLock().unlock();
}
}
try {
// 使用数据...
} finally {
rwLock.readLock().unlock();
}
}
核心数据结构
ReentrantReadWriteLock
的底层实现依赖于AQS(AbstractQueuedSynchronizer),其核心数据结构包括:
状态变量(state)
// 高16位表示读锁数量,低16位表示写锁数量
volatile int state;
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 读锁计数单位
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 最大读锁数65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 写锁掩码
锁持有者记录
// 写锁持有者线程
private transient Thread owner;
// 读锁持有线程及计数
private transient ThreadLocalHoldCounter readHolds;
private transient HoldCounter cachedHoldCounter;
AQS 核心概念
AQS 是 Java 并发包中构建锁和同步器的核心框架,ReentrantLock
、Semaphore
、CountDownLatch
等都基于 AQS 实现。
AQS 核心思想
-
CLH 队列:基于双向链表的 FIFO 等待队列
-
状态变量:
volatile int state
表示同步状态 -
模板方法:提供
tryAcquire
/tryRelease
等模板方法供子类实现
重要组件
// 同步队列头尾节点
private transient volatile Node head;
private transient volatile Node tail;
// 同步状态
private volatile int state;
// 节点状态
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter; // 用于条件队列
}
AQS 核心源码解析
获取资源(acquire)
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取资源
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并等待
selfInterrupt();
}
// 子类需要实现的方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
addWaiter 方法
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // 队列为空时初始化
return node;
}
acquireQueued 方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { // 前驱是头节点且获取成功
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 检查是否需要阻塞
parkAndCheckInterrupt()) // 阻塞线程
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
释放资源(release)
public final boolean release(int arg) {
if (tryRelease(arg)) { // 子类实现释放逻辑
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继节点
return true;
}
return false;
}
unparkSuccessor 方法
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒线程
}
AQS 共享模式实现
共享获取(acquireShared)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r); // 设置头节点并传播
p.next = null;
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
共享释放(releaseShared)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h); // 唤醒后继节点
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head) // 如果头节点变化则继续
break;
}
}
AQS 条件队列实现
条件等待(await)
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // 添加到条件队列
int savedState = fullyRelease(node); // 完全释放锁
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // 不在同步队列中
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && // 重新获取锁
interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters(); // 清理取消的节点
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
条件通知(signal)
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && // 转移到同步队列
(first = firstWaiter) != null);
}
AQS 实现类分析
ReentrantLock 实现
// 非公平锁实现
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;
}
CountDownLatch 实现
// 共享模式实现
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
AQS 设计精髓
-
模板方法模式:定义算法骨架,子类实现具体逻辑
-
CLH队列变种:改进自旋锁为阻塞锁
-
状态管理:
state
变量实现灵活同步控制 -
公平性控制:通过队列顺序保证公平性
常见面试问题
-
AQS 的底层数据结构是什么?
-
CLH 变种队列 + volatile state
-
-
AQS 为什么使用双向链表?
-
便于取消节点时快速找到前驱
-
-
AQS 如何保证公平性?
-
严格按照队列顺序获取锁
-
-
AQS 的共享模式和独占模式区别?
-
独占模式:同一时刻只有一个线程能获取
-
共享模式:多个线程可以同时获取
-
-
AQS 的条件队列和同步队列关系?
条件队列节点被唤醒后会转移到同步队列
读锁实现机制
读锁获取流程
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果有写锁且不是当前线程持有的,获取失败
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 读锁获取成功后的处理
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// 更新线程本地读锁计数
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
读锁释放流程
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 处理firstReader的情况
if (firstReader == current) {
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
// 更新线程本地读锁计数
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
// CAS更新state
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
写锁实现机制
写锁获取流程
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// 存在读锁或其他线程持有写锁
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
// 尝试获取写锁
if ((w == 0 && writerShouldBlock()) ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
写锁释放流程
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
公平性实现
公平模式
// 公平锁的判断逻辑
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
非公平模式
// 非公平锁的判断逻辑
final boolean writerShouldBlock() {
return false; // 写锁可以直接尝试获取
}
final boolean readerShouldBlock() {
// 防止写线程饥饿
return apparentlyFirstQueuedIsExclusive();
}
性能优化关键点
-
读锁计数优化:
-
使用
firstReader
和firstReaderHoldCount
优化第一个读线程的计数 -
使用
cachedHoldCounter
缓存最近一个读线程的计数 -
使用
ThreadLocal
存储各线程的读锁计数
-
-
状态更新优化:
-
使用CAS操作更新state
-
读锁释放采用自旋CAS
-
-
队列管理优化:
-
非公平模式下防止写线程饥饿
-
公平模式下严格按队列顺序获取锁
-
典型问题分析
为什么读锁不能升级为写锁?
可能导致死锁:
线程A获取读锁
线程B尝试获取写锁(被阻塞)
线程A尝试升级为写锁(等待线程B释放读锁) → 死锁
为什么锁降级是安全的?
因为降级过程中写锁始终由当前线程持有,不会有其他线程能修改数据。
如何避免写线程饥饿?
非公平模式下通过apparentlyFirstQueuedIsExclusive()
检查队列头部是否是写锁请求。
总结
ReentrantReadWriteLock
的底层实现体现了以下设计思想:
-
状态压缩:32位int同时表示读锁和写锁状态
-
性能优化:读锁计数采用多种缓存策略
-
公平性控制:通过
shouldBlock
方法实现不同策略 -
安全性保证:严格的状态检查和CAS操作
理解这些底层机制,可以帮助我们更好地使用和优化读写锁,写出更高效的并发程序。
专栏合集在这里:
👉 《Java集合框架深度解析:从源码到实战应用》(已完结)
👉 《Java高并发实战:原理、源码与性能调优》(进行中)
📣 互动邀请
如果本文对您有帮助,请不要吝啬您的支持:
👍 点赞 - 您的点赞是我持续创作的最大动力!
🔔 关注 - 获取更多Java核心技术解析和实战技巧
💬 评论 - 有任何问题或建议欢迎留言讨论
🚀 下期预告
接下来我将继续深入讲解:
《高并发必备(九)!读写锁的进化版?深入理解StampedLock的乐观读与锁升级机制》