并发编程的核心是对状态变量的管理和维护,使其在多线程下同样具备安全性、可靠性及良好的伸缩性,因此你需要设计一套针对并发环境下的有良好的状态依赖性的管理策略。一般人都会采用条件队列或者显示的condition对象或者标准的同步容器。相信前两种大多数人很熟悉了,就不做记录了,这里我想对几种同步容器做下学习笔记。
从最简单开始,闭锁CountDownLatch
可以看到闭锁还是很简单的,它有一个内部类Sync扩展了AbstractQueuedSynchronizer,内部维护者一个state的整形变量。闭锁采用的是共享模式,因此它实现了tryAcquireShared和tryReleaseShared方法,这两种方法的不同实现产生了多种同步容器。
接下来通过测试代码分析源码
final int taskCount=1;
CountDownLatch downLatch=new CountDownLatch(taskCount);
for (int i = 0; i <taskCount*10 ; i++) {
new Thread(()->{
try{
System.out.println(Thread.currentThread().getName()+" 开始!。。。");
//等待条件
downLatch.await();
System.out.println(Thread.currentThread().getName()+" 完成!。。。");
}catch (java.lang.InterruptedException e){
//todo...
}
}).start();
}
Thread.sleep(3*1000);
//释放条件
downLatch.countDown();
从结果大致可以看出await()方法会进入阻塞,直到countdown()方法释放条件后线程会依序执行。
-> 获取通知,条件状态不满足阻塞
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
调用内部类的acquireSharedInterruptibly(1),获取通知,采用共享模式并支持中断。
->acquireSharedInterruptibly(1)
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
获取通知前检测是否中断,接着调用Sync中实现的tryAcquireShared(1)
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
可以看到在state减少到0之前,返回值为-1<0,这里说下,state初始值为构建函数中传递的值,我设置为1。
->
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);
}
}
这里首先会调用addWaiter()添加节点
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
熟悉并发原语的应该了解了,这里采用的是cas操作增加节点,属于非阻塞算法。
节点创建好了后会有一无限循环,看似自旋,其实一般2次就可以进入阻塞。
p==head条件只有第一个入队后才会触发,即使触发了,tryAcquireShared返回值也是1,无法推进节点 setHeadAndPropagate(node, r)。
每次循环都会进入shouldParkAfterFailedAcquire(p, node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
第一次进入的时候将waitStatus状态设置为-1,并返回false,第二次返回true,接着调用parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
LockSupport阻塞该线程,达到阻塞状态。
->唤醒
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
只有当state减少到0时,才触发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;
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;
}
}
该方法从head节点开始执行,检测其等待状态,如果属于等待信号状态(SIGNAL = -1),唤醒h.next的执行线程;
如果等于初始状态0,会设置其状态为推进(PROPAGATE = -3),这种情况你可以认为释放过程在协助节点推进。
多次释放的过程中可能会出现h!=head的情况,这时我们会进一步往以下节点释放。
->阻塞线程被唤醒
parkAndCheckInterrupt()检测线程是否中断,没有中断会执行推进方法。
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();
}
}
将节点置换成当前节点,并进行下一步的释放。
至此阻塞到唤醒的过程就全部完成了,作者采用一系列非阻塞算法,并依循程序先行发生规则,完美的设计。
再次向Doug Lea致敬!!!。