上一篇我们详细讲解了互斥锁的源码实现,也即同一时刻只有一个线程获得锁,其它参与竞争的线程必须等待。有一种情况是允许多个线程同时获得锁,如读写锁(ReentrantReadWriteLock)允许所有读线程同时获取锁,写线程阻塞,也即读读共享,读写互斥,写写互斥。那么接下来我们就来看看共享锁的源码实现。
获取共享锁,该方法不响应中断,如果希望它响应中断请调用 acquireSharedInterruptibly 方法。acquireShared 方法原理同 acquire 原理一样,通过调用 tryAcquireShared 方法获取锁(子类实现),获取锁失败将进一步调用 doAcquireShared 方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
实现流程同 acquireQueued 方法,读者可以对比着学习。
1、包装 Node 节点,将其加入竞争队列,注意此处节点模式为 SHARED 模式
2、获取当前节点的 prev 节点,判断是否是头结点,是,尝试获取一次锁,获取锁成功调用 setHeadAndPropagate 方法,更新头结点并唤醒后续 SHARED 节点
3、再次尝试获取锁失败,则进入 OS 阻塞等待
private void doAcquireShared(int arg) {
// 以 SHARED 模式添加到竞争队列,addWaiter 方法详解请看上一篇的【互斥锁源码分析】
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取 prev 节点信息
final Node p = node.predecessor();
if (p == head) {
// 是头结点,尝试获取锁
int r = tryAcquireShared(arg);
if (r >= 0) {
// 获取锁成功,更新头结点,如果当前是 SHARED 模式,那么继续唤醒后续 SHARED 模式的节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted) // 如果发生中断,重新设置中断标志位
selfInterrupt();
failed = false;
return;
}
}
// shouldParkAfterFailedAcquire 与 parkAndCheckInterrupt 方法已在上篇【互斥锁源码分析】中详细讲解过,此处不做过多赘述
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // 保存当前执行时的头结点 head
setHead(node);// 更新头结点
// propagate 为 tryAcquireShared 方法的返回值,大于 0 表明获取到了锁,第一个 h.waitStatus < 0 是当前线程执行时的头结点,第二个 h.waitStatus 是更新后的头结点状态,也即当前节点的状态
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())// 当前节点的 next 节点如果是 SHARED 模式,继续唤醒
doReleaseShared();
}
}
可响应中断的方式获取锁
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程被中断,直接抛出中断异常
if (Thread.interrupted()) // currentThread().isInterrupted(true)
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // 获取锁失败 调用 doAcquireSharedInterruptibly 以响应中断的方式加入竞争队列等待
doAcquireSharedInterruptibly(arg);
}
支持响应中断的方式被唤醒,其他逻辑同 doAcquireShared 方法
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException(); // 被中断时 抛出中断异常
}
} finally {
if (failed)
cancelAcquire(node);
}
}
释放共享锁,释放成功调用 doReleaseShared 唤醒后续 SHARED 等待节点
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 子类实现具体释放逻辑
doReleaseShared();
return true;
}
return false;
}
共享锁释放成功后,该线程将负责唤醒其 next 节点(SHARED 节点),在多线程并发条件下,该线程可能还需要帮助唤醒其他的 SHARED 等待节点(当看到 Semaphore 信号量的实现时你将会明白 PROPAGATE 的用处)
private void doReleaseShared() {
for (;;) {
Node h = head; // 保存当前头结点,保证后续操作的一致性
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 将 状态更新为 0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // CAS 失败,重复执行
unparkSuccessor(h);// 唤醒 next 节点
}
else if (ws == 0 && // 多线程并发同时释放了锁,如 Semaphore 信号量,此时 CAS 更新 节点状态为 PROPAGATE
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // 头结点未发生变化 直接退出循环,如果头结点发生了变更,将要重复循环,帮忙唤醒后续需要唤醒的节点
break;
}
}