文章中的源码均来自JDK1.8
1. 什么是共享模式
共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取同步状态。
如上图,左边是共享式访问资源时,其他共享式的访问均被允许,而独占式访问被阻塞,右边式独占式访问资源时,同一时刻其他访问均被阻塞。
2. 基于共享模式的共享锁示例
public class SharedLock {
private Sync sync = null;
public SharedLock (int count) {
sync = new Sync(count);
}
static final class Sync extends AbstractQueuedSynchronizer {
Sync (int count) {
if (count <= 0) {
throw new IllegalArgumentException("count的值必须大于0");
}
this.setState(count);
}
@Override
public int tryAcquireShared(int acquires) {
for (;;) {
int available = this.getState();
int remaining = available - acquires;
if (remaining < 0 ||
this.compareAndSetState(available, remaining))
return remaining;
}
}
@Override
public boolean tryReleaseShared(int releases) {
for (;;) {
int current = this.getState();
int next = current + releases;
if (next < current)
throw new Error("共享访问数越界");
if (this.compareAndSetState(current, next))
return true;
}
}
}
/**
* 获取
*/
public void lock () {
sync.acquireShared(1);
}
/**
* 释放
*/
public void unlock () {
sync.releaseShared(1);
}
}
上面的示例中,SharedLock的内部类Sync继承了AQS,重写tryAcquireShared和tryReleaseShared接口,并在构造函数调用setState方法设置同步状态state,代表同时可以有多少个线程进行访问。对于同步状态的获取,同步器会先计算出获取后的同步状态,然后通过CAS设置state,当返回值大于等于0时,说明当前线程才获取同步状态,对于SharedLock而言,表示当前线程获取到锁。
2. 源码解析
2.1 共享式同步状态获取和
SharedLock中获取锁是通过Sync的实例来调用acquireShared方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
tryAcquireShared方法是子类重写方法,当该方法返回值 < 0 时说明线程没有获取到同步状态,则进入doAcquireSared方法
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) { // 前驱节点为头节点的话再次调用tryAcquireShared获取状态
int r = tryAcquireShared(arg);
if (r >= 0) { // 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);
}
}
这里的代码跟独占式中的acquireQueue方法有点相似,首先是获取前驱节点,如果前驱节点为头节点的话可以调用traAcquireShared方法获取同步状态,当返回的值r >= 0 说明获取到同步状态,则进入setHeadAndPropagate方法,否则的话跟独占式一样这里就不多解释,我们直接来看setHeadAndPropagate方法
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node); // 设置头节点
// propagate > 0 或者 head的 waitStatus < 0 的话则需要继续唤醒后继节点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared()) // 后继节点必须也是共享式访问才能唤醒
doReleaseShared();
}
}
propagate是tryAcquireShared返回的参数,代表还能有多少个线程可以获取到同步状态
这里先把获取到同步状态的节点设置为头节点,然后判断 propagate 是否大于0,或head是否等于null,或head的waitStatus是否小于0,如果条件成立则获取后继节点,如果后继节点为共享式访问的话则需要调用doReleaseShared唤醒后继节点
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // SIGNAL说明后继节点需要唤醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
进来该方法后首先判断头节点的waitStatus是不是SIGNAL,如果是则调用unparkSuccessor唤醒后继节点,这个和独占式一样就不多讲,否则将waitStatus设置为SIGNAl。如果在这过程中有其他线程获取到同步状态并设置重新设置头节点话,将会继续唤醒后面的节点,否则退出返回。
共享式同步状态的释放是调用了releaseShared方法,而在这方法中也是调用了doReleaseShared方法来进行释放的。
参考资料:《Java并发编程的艺术》