java并发之AQS共享模式源码解读
上一篇文章简单解读了AQS的源码以及独占模式下获取锁的流程以及细节,碍于篇幅,这篇文章来说说AQS的共享模式。
共享锁的一个实现类就是信号量Semaphore,Semaphores一般用于对某种访问资源的限制,一个信号量相当于持有一些许可(permits),线程可以调用Semaphore对象的acquire()方法获取一个许可,调用release()来归还一个许可。
同样Semaphore有公平和非公平两种模式。默认就是非公平模式。
先看下AQS的acquireShared()方法
/**
* Acquires in shared mode, ignoring interrupts. Implemented by
* first invoking at least once {@link #tryAcquireShared},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquireShared} until success.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquireShared} but is otherwise uninterpreted
* and can represent anything you like.
*/
public final void acquireShared(int arg) {
/**
tryAcquireShared()方法由自定义同步器实现,
*/
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
/**
* Acquires in shared uninterruptible mode.
* @param arg the acquire argument
*/
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);//构造shared node加入队列
boolean failed = true;//是否成功
try {
boolean interrupted = false;//是否被中断
for (;;) {//自旋等待
final Node p = node.predecessor();//获取前驱结点
if (p == head) {//前驱结点是head的情况下
int r = tryAcquireShared(arg);//负数表示获取失败 0 表示获取成功,没有剩余资源 正数表示获取成功,还有剩余资源
if (r >= 0) {//如果还有剩余资源,通知后面等等的共享节点
setHeadAndPropagate(node, r);//设置head结点,并且通知后继等待的共享结点
p.next = null; // help GC 帮助回收
if (interrupted)//如果中断
selfInterrupt();//响应中断
failed = false;
return;
}
}
// 判断状态,寻找安全点,进入waiting状态,等着被unpark()或interrupt()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* Sets head of queue, and checks if successor may be waiting
* in shared mode, if so propagating if either propagate > 0 or
* PROPAGATE status was set.
*
* @param node the node
* @param propagate the return value from a tryAcquireShared
*/
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) 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.
*/
//这里意思有两种情况是需要执行唤醒操作
//1.propagate > 0 表示调用方指明了后继节点需要被唤醒,其实就是还有剩余资源的意思
//2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
//如果后继节点为空,或者后继节点是共享模式的节点 则唤醒
if (s == null || s.isShared())
doReleaseShared();
}
}
acquire()总结
- tryAcquireShared()尝试获取资源,成功则直接返回,否则进入流程2。
- doAcquireShared()会将当前线程加入等待队列尾部休息,直到其他线程释放资源唤醒自己。它还会尝试着让唤醒传递到后面的节点。
其实跟acquire()的流程大同小异,只不过多了个自己拿到资源后,还会去唤醒后继队友的操作(这才是共享嘛)。
接下来看下releaseShared()
/**
* Releases in shared mode. Implemented by unblocking one or more
* threads if {@link #tryReleaseShared} returns true.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryReleaseShared} but is otherwise uninterpreted
* and can represent anything you like.
* @return the value returned from {@link #tryReleaseShared}
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
/**
* Release action for shared mode -- signal successor and ensure
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
/*
* 如果head需要通知下一个节点,调用unparkSuccessor
* 如果不需要通知,需要在释放后把waitStatus改为PROPAGATE来继续传播
* 此外,我们必须通过自旋来CAS以防止操作时有新节点加入
* 另外,不同于其他unparkSuccessor的用途,我们需要知道CAS设置状态失败的情况,
* 以便进行重新检查。
*/
for (;;) {
//唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
//其实就是唤醒上面新获取到共享锁的节点的后继节点
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//表示后继节点需要被唤醒
if (ws == Node.SIGNAL) {
//这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
//执行唤醒操作
unparkSuccessor(h);
}
//如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
//如果头结点没有发生变化,表示设置完成,退出循环
//如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
if (h == head)
break;
}
}
到这里,AQS的解读先告一段落。