一、CountDownLatch源码分析
1.构造函数
java
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
2.内部类Sync
java
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//减完之后是0才会返回true
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;
}
}
}
3.await函数
java
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
java
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
-
await() 调用 AQS 的 acquireSharedInterruptibly(1)。
-
AQS 回调 Sync 重写的 tryAcquireShared(1)。
-
如果是 0: 如果 state == 0,则直接返回,不阻塞。
-
关键判断: 如果 state != 0,tryAcquireShared 返回 -1,表示获取失败,执行doAcquireSharedInterruptibly
-
AQS 会将当前线程封装成 Node 放入等待队列,并将其挂起(LockSupport.park)。
java
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//将当前节点加入等待队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
//如果前驱节点是 head,说明当前节点是队列中的第一个等待者,尝试获取锁
if (p == head) {
int r = tryAcquireShared(arg);
//获取成功,设置自己为新的 head,并唤醒后续的共享模式节点
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);
}
}
4.countDown()
java
public void countDown() {
sync.releaseShared(1);
}
java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
-
countDown() 调用 AQS 的 releaseShared(1)。
-
AQS 回调 Sync 重写的 tryReleaseShared(1)。
-
在 tryReleaseShared 中,使用 CAS + 自旋 的方式将 state 减 1。
-
唤醒时机: 只有当 state 从 1 变为 0 时,tryReleaseShared 才会返回 true。
-
AQS 收到 true 后,调用 doReleaseShared() 唤醒同步队列中所有因为 await() 而阻塞的线程。
java
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;
//唤醒下一个await节点
unparkSuccessor(h);
}
//尝试将其改为 PROPAGATE (-3),确保后续获取锁时能继续向后传播
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果在处理过程中 head 没有变,说明唤醒动作已经平稳传递,可以退出
// 如果 head 变了(说明被唤醒的线程已经抢占了 head),必须继续循环检查新 head 的后继
if (h == head)
break;
}
}
二、CutDownLatch与join的区别
|-----------|-----------------------------|----------------------------------------|
| 特性 | Thread.join() | CountDownLatch |
| 颗粒度 | 线程级。必须等待线程对象执行结束(死亡)。 | 任务级。可以在任务执行过程中的任何地方计数,不要求线程结束。 |
| 灵活性 | 较低。只能等待整个线程运行完。 | 较高。一个线程可以多次 countDown,或者一个任务完成后继续做别的事。 |
| 等待关系 | 通常是一个线程等待另一个线程。 | 可以是多个线程等待多个线程(多对多)。 |
| 原理 | 基于 Object.wait() 和线程存活状态检查。 | 基于 AQS 的共享模式状态(state)。 |
| 使用便利性 | 简单,直接对线程对象操作。 | 稍微复杂,需要手动管理计数器。 |
使用线程池来管理线程时一般都是直接添加Runable到线程池,这时候就没有办法再调用线程的join方法了,就是说countDownLatch相比join方法让我们对线程同步有更灵活的控制。
三、回环屏障 CyclicBarrier
上节介绍的CountDownLatch在解决多个线程同步方面相对于调用线程的join方法已经有了不少优化,但是CountDownLatch的计数器是一次性的,也就是等到计数器值变为0后,再调用CountDownIatch的await和countdown方法都会立刻返回,这就起不到线程同步的效果了。所以为了满足计数器可以重置的需要,JDK开发组提供了CyclicBarrier类,并且CyclicBarrier类的功能并不限于CountDownLatch的功能。从字面意思理解,CyclicBarrier是回环屏障的意思,它可以让一组线程全部达到一个状态后再全部同时执行。这里之所以叫作回环是因为当所有等待线程执行完毕,并重置CyclicBarrier的状态后它可以被重用。之所以叫作屏障是因为线程调用await方法后就会被阻塞,这个阻塞点就称为屏障点,等所有线程都调用了await 方法后,线程们就会冲破屏障,继续向下运行。
1.线程同步
java
package cn.tx.sycbarri;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CycleBarrierTest1 {
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread()+"task1 merge result");
}
});
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread()+"task1-1");
System.out.println(Thread.currentThread()+"enter in barrier");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+"enter out barrier");
} catch (Exception e) {
e.printStackTrace();
}
}
});
service.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread()+"task1-2");
System.out.println(Thread.currentThread()+"enter in barrier");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+"enter out barrier");
} catch (Exception e) {
e.printStackTrace();
}
}
});
service.shutdown();
}
}
如上代码创建了一个CyclicBarrier对象,其第一 个参数为计数器初始值,第二个参数Runable是当计数器值为0时需要执行的任务。
在main函数里面首先创建了一个大小为2的线程池,然后添加2个子任务到线程池,每个子任务在执行完自己的逻辑后会调用await方法。
一开始计数器值为2,当第一个线程调用await方法时,计数器值会递减为1。由于此时计数器值不为0,所以当前线程就到了屏障点而被阻塞。然后第二个线程调用await时,会进入屏障,计数器值也会递减,现在计数器值为0,这时就会去执行CyclicBarrier构造函数中的任务,执行完毕后退出屏障点,并且唤醒被阻塞的第二个线程,这时候第一个线程也会退出屏障点继续向下运行。上面的例子说明了多个线程之间是相互等待的,假如计数器值为N,那么随后调用await方法的N-1个线程都会因为到达屏障点而被阻塞,当第N个线程调用await后,计数器值为0了,这时候第N个线程才会发出通知唤醒前面的N-1个线程。也就是当全部线程都到达屏障点时才能一块继续向 下执行。对于这个例子来说,使用CountDownLatch也可以得到类似的输出结果。
2.可复用性
java
package cn.tx.sycbarri;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CycleBarrierTest2 {
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread()+"step-1");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+"step-2");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+"step-3");
} catch (Exception e) {
e.printStackTrace();
}
}
});
service.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread()+"step-1");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+"step-2");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+"step-3");
} catch (Exception e) {
e.printStackTrace();
}
}
});
service.shutdown();
}
}

在如上代码中,每个子线程在执行完阶段 1 后都调用了 await 方法,等到所有线程都 到达屏障点后才会 - 块往下执行,这就保证了所有线程都完成了阶段 1 后才会开始执行阶 段 2 。然后在阶段 2 后面调用了 await 方法,这保证了所有线程都完成了阶段 2 后,才能 开始阶段 3 的执行。这个功能使用单个 CountDownLatch 是无法完成的。
