共享模式下的acquireShared()和releaseShared()
首先来看看acquireShared()代码,
public final void acquireShared(int arg) {
// 尝试获取资源如果成功直接返回
if (tryAcquireShared(arg) < 0)
// 当前线程调用doAcquireShared()加入同步队列
doAcquireShared(arg);
}
tryAcquireShared()方法同样由自定义同步器实现,用来给state原子的加一些操作。如果tryAcquireShared()<0说明当前线程尝试获取资源失败需要进入同步队列,来看看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) {
// 当前节点尝试获取资源,如果r>=0
// 说明有资源可以获取
int r = tryAcquireShared(arg);
if (r >= 0) {
// 与独享模式不一样的是,当前节点获取到资源后
// 会unpark()当前节点的下一个节点进行tryAcquireShared()
setHeadAndPropagate(node, r);
p.next = null; // help GC
// 如果在等待过程中,外界给与中断干扰
// 则获取到资源后会响应中断(有可能是取消使用资源)
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 与独享模式一样,前一个节点是SIGNAL时
// 进行park挂起当前线程等待唤醒
// 如果前一个节点不是SIGNAL或者前一个节点被取消
// 需要从尾部找到前一个非null节点
// 最后将前一个节点置为SIGNAL状态并继续执行for循环
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);
// 唤醒头节点的下一个节点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
与独享模式不一样的是,当前节点获取到资源后还会唤醒下一个节点进行资源的获取。
再来看看releaseShared()方法,
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
首先尝试释放资源方法,如果前一个或几个线程已经释放了共享资源(与独享模式不一样的是,独享模式保证当state最初设定的值时才会唤醒下一个节点),那么tryReleaseShared()方法会返回true,然后执行doReleaseShared()方法唤醒下一个节点,被唤醒的节点会尝试tryAcquireShared()方法。
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 头节点的状态为SIGNAL唤醒后继节点
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;
}
}
共享模式流程
- 假定我们有10个资源,A线程正在占用7个,B线程正在占用2个,剩余1个资源
- 这时C线程需要获取资源,但是需要4个资源才能继续执行,所以C线程到来时初始化同步队列并将C线程加入到同步队列
- 此时A线程释放掉一个资源,唤醒头节点的下一个节点尝试资源的获取,此时共有2个资源可以获取,所以C获取资源失败
- 同时又来了一个线程D尝试获取资源,D需要3个资源才能继续执行,D加入同步队列进行挂起等待资源获取
- 此时A再次释放了一个资源,唤醒头节点下一个节点,失败(注意!!!这里体现共享锁的公平性与非公平性,A再次释放资源后空闲3个资源,而D正好需要3个资源就可以继续执行,如果是公平的共享锁则只会按照队列顺序进行排队获取资源;如果是非共享锁,则D此时可以获取资源进行下一步操作)
- A再次释放2个资源,B释放2个资源,尝试唤醒C,成功,并且C成为头节点后再次唤醒D也成功(体现共享的一步),头节点执行的节点是获取到资源的节点