AQS 体系中针对不同的场景设计了不同的锁的实现,针对读多写少的场景,AQS 提供了 ReentrantReadWriteLock 读写锁来提高锁的效率,本篇我们一起走进它的实现原理。
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
// 尝试获取失败,自旋获取锁
// 执行到这里,说明前面尝试获取锁的时候,其他线程持有了排他锁(写锁)
// 或者当前线程是首次取获得读锁,但是队列中的第一个线程是等待读锁的线程
// 因此 doAcquireShared 方法中通过 setHeadAndPropagate 方法传播
doAcquireShared(arg);
}
tryAcquireShared()
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
// 读写锁的时候,int 型的 state 的高 16 位用来记录共享锁的获得次数,
// 低16位用来记录排他锁的重入次数
int c = getState();
// 排他锁被被获取的次数不为0,且锁的 owner 不是当前线程,返回失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
// 这里之所以调用 readerShouldBlock() 方法判断头节点的后继节点是否在等待写锁,
// 是为了防止写锁的饥饿,假如在写锁获取之前已经有线程获得了读锁,
// 这里不进行判断就会导致后续获取读锁的线程会一直获得成功,从而使等待写锁的线程一直阻塞
if (!readerShouldBlock() &&// 这个方法下面我们重点看
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {// 说明共享锁被首次获取
firstReader = current;
firstReaderHoldCount = 1;
}
// firstReader 获得共享锁的次数会被缓存在 Sync 中
// 如果当前线程是 firstReader,将缓存的数值自增
else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// 每个线程获得锁的数量也会被缓存在 Sync 中,每次获得锁成功要进行自增
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
readerShouldBlock()
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
// 头节点的后继节点在等待获取排他锁,则返回 true
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
fullTryAcquireShared()
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
// 如果当前线程是锁的 owner,直接执行下面获取共享锁的逻辑
}
// 如果排他锁被获取的次数为 0,先判断头节点的后继节点是否在等待排他锁,
// 如果是的话,再判断获得锁的线程是否为当前线程
else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
// cachedHoldCounter 缓存的是最后一次成功获得共享锁的线程持有的锁的数量
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();// 当前线程的 HoldCounter
if (rh.count == 0)
// 如果当前线程持有的共享锁的数量为 0,将当前线程对应的计数器移除
readHolds.remove();
}
}
// 如果当前线程持有的共享锁的数量为 0,返回失败
if (rh.count == 0)
return -1;
}
}
// 代码执行到这里,说明当前线程持有写锁或者
// readerShouldBlock() 方法返回 false 或者
// readerShouldBlock() 方法返回 true 且当前线程是第一个获得共享锁的线程或者
// readerShouldBlock() 方法返回 true 且当前线程持有的共享锁的数量不为0
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 将共享锁的数量加1,同时更新缓存的信息以及计数器
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
doAcquireShared()
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; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate()
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 如果后继节点是在等待共享锁,将其唤醒
if (s == null || s.isShared())
doReleaseShared();
}
}
doReleaseShared()
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 至少经过两次循环,将 waitStatus 由 SIGNAL 更新成 PROPAGATE
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 当前节点唤醒后继节点后,后继节点发现自己的前驱节点为头节点,
// 会去获取共享锁,获取成功后,将自己设置成新的头节点,
// 因此当前线程执行到这里时,头节点很有可能已经发生变化,
// 假如头节点已变,继续循环来唤醒新的头节点的后继节点,
// 直至遇到等待读锁的节点或者到队尾
if (h == head) // loop if head changed
break;
}
}
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 当 state 中记录的共享锁的数量为 0 时,才会进入这里
// 这个方法在上面已经分析过,主要就是唤醒后继等待共享锁的节点
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared()
protected final boolean tryReleaseShared(int unused) {
// 更新线程计数器的值
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
// 更新 state 中共享锁的获取数量
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// 尝试获得锁失败,将当前线程构造成 node 节点并加入队列自旋
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
// state 不为 0,如果写锁的数量为 0,说明读锁已被线程获取,此时获取失败
// 如果写锁的数量不为 0,且当前线程不是锁的 owner,获取失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
// 执行到这里,写锁的数量不为 0 且当前线程是写锁的 owner,
// 因此这里不需要 CAS,直接重入即可
setState(c + acquires);
return true;
}
// 执行到这里,说明 state == 0 成立,说明当前既无线程获得共享锁也无线程获得排他锁,
// 但是有可能存在读锁的竞争,因此这里使用 CAS 尝试获得锁
if (writerShouldBlock() || // 对于非公平锁而言,writerShouldBlock() 总是返回 false
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
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);
}
}
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒后继节点
unparkSuccessor(h);
return true;
}
// 与共享锁的逻辑类似,当排他锁被完全释放时才返回 true,否则返回 false
return false;
}
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
// 锁被完全释放了,将 owner 置空
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
现在我们来结合具体的场景来分析一下读写锁的工作过程。
假如现在有一个线程 A 去获取读锁
如果此时没有线程持有写锁(即 state 中记录的排他锁的数量为 0),则可以直接获取成功,获取成功后只需要将当前线程缓存的计数器及 state 中记录的总读锁的数量更新。
如果此时有线程已经获得了写锁(即 state 中记录的排他锁的数量不为 0),且锁的 owner 不是 A 而是 B,则 A 会先尝试去获取锁,假如在尝试的时候 B 已经释放锁,A 可以获取成功,否则线程 A 会被构造成 node 节点加入等待队列,直到前驱节点将其唤醒,被唤醒后 A 会重新尝试获取读锁,获取成功后将自己设置成 head、更新计数并唤醒等待读锁的后继节点(在等待的过程中,可能会有其他尝试获取读锁的线程也加入了队列)。
如果此时有线程已经获得了写锁(即 state 中记录的排他锁的数量不为 0),且锁的 owner 就是 A,则 A 只需要将当前线程缓存的计数器及 state 中记录的总读锁的数量更新。
当 A 释放锁的时候,会更新当前线程缓存的计数器及 state 中记录的读锁的数量,并判断 state 中记录的总读锁的数量是否到达 0,即是否可以完全释放读锁,如果是则唤醒队列中的后继节点(如果存在)。
假如现在有一个线程A去获取写锁
如果此时没有线程持有写锁或读锁(即 state 中记录的排他锁、共享锁的数量均为 0),则可以直接获取成功,获取成功后只需要将 state 中记录的总写锁的数量更新。
如果此时有线程已经获得了读锁(即 state 中记录的读锁的数量不为 0),则 A 会被构造成 node 节点加入等待队列,直到前驱节点将其唤醒,被唤醒后 A 会重新尝试获取写锁,获取成功后将自己设置成头节点。
如果此时有线程已经获得了写锁(即 state 中记录的排他锁的数量不为 0),且锁的 owner 不是 A,则 A 会被构造成 node 节点加入等待队列,直到前驱节点将其唤醒,被唤醒后 A 会重新尝试获取写锁,获取成功后将自己设置成头节点。
如果此时有线程已经获得了写锁(即 state 中记录的排他锁的数量不为 0),且锁的 owner 就是 A,则可以直接获取成功(重入),获取成功后只需要将 state 中记录的总写锁的数量更新。
当 A 释放锁的时候,会更新 state 中记录的读锁的数量,并判断 state 中记录的总写锁的数量是否到达 0,即是否可以完全释放写锁,如果是则唤醒队列中的后继节点(如果存在)。