CountDownLatch说明
CountDownLatch是一个用来统计线程个数的工具,可以看作是个门闩(latch,门闩),必须等到线程齐了后才打开门闩。
CountDownLatch主要使用的方法是:
- await,当前线程阻塞
- countDown,当前线程释放锁。
CountDownLatch的两个应用场景:
1.乘车去买菜,也可看作是工人上工和下工。这种场景需要的使用两个CountDownLatch。代码如下:
private static final CountDownLatch people = new CountDownLatch(3);
private static final CountDownLatch car = new CountDownLatch(1);
static class Run3 implements Runnable {
private String name;
Run3(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println(name + "上车");
car.await();
System.out.println(name + "去采购东西");
// doWork部分,不同线程耗时不一样
int second = new Random().nextInt(20);
Thread.sleep(second * 1000);
System.out.println(name + "采购东西完毕,回到车上");
people.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("人员准备去采购");
new Thread(new Run3("甲 ")).start();
new Thread(new Run3("乙 ")).start();
new Thread(new Run3("丙 ")).start();
Thread.sleep(10 * 1000);
// doWork
car.countDown();
System.out.println("车辆到达菜市场,人员下车");
// 等待所有people完成采购
people.await();
System.out.println("车辆回家");
}
输出日志:
人员准备去采购
甲 上车
乙 上车
丙 上车
车辆到达菜市场,人员下车
乙 去采购东西
甲 去采购东西
丙 去采购东西
甲 采购东西完毕,回到车上
丙 采购东西完毕,回到车上
乙 采购东西完毕,回到车上
车辆回家
2.另一个案例是单独的CountDownLatch完成的,可以看作是上述代码中的people。这个案例是把一个大任务分成了多个小任务让不同的人(线程)去处理,等所有人(线程)完成后,这个任务才算完成。可以类比的案例是,赛场跑道上多个运动员比赛,每个运动员都完成了比赛。那这个比赛项目就结束了。又或者是一个宝藏密室需要多个钥匙都插入钥匙孔才能打开大门。
CountDownLatch的源码分析
CountDownLatch 内部也是有个Sync类继承了AbstractQueuedSynchronizer类,之前我们分析ReentrantLock、Semaphore时,发现这个父类他只提供模板方法。然而子类以不同的实现方式,来对应具体的业务案例。
CountDownLatch 的Sync类如下:
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;
}
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;
}
}
}
// CountDownLatch的await方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// AQS类的acquireSharedInterruptibly方法,也就是CountDownLatch实际执行的方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
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);
}
}
// CountDownLatch方法
public void countDown() {
sync.releaseShared(1);
}
// AQS的releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
从这简单的代码里,我们可以了解到以下内容:
- 通过构造方法,直接设置了AQS类中state的具体值,同时要求了执行线程的个数要和这个值保持一致。
- 主线程通过await方法被阻塞,说明state !=0,方法返回了-1。当前线程被放在了队列中,且被挂起了。
- 子线程调用countDown方法,实际上是将state值进行减少,因为每个线程只能减1,那么当线程个数等于初始state的时候,state才能被减到0,那么此时AQS的releaseShared方法里的tryReleaseShared(arg)方法得到了true,此时才能执行doReleaseShared方法,唤醒主线程。
- 结合案例,主线程和子线程其实是相对的概念。不能说main方法里就一定是主线程。