Chapter 8 浅析CountDownLatch类

来源:互联网 发布:g76内螺纹编程实例 编辑:程序博客网 时间:2024/06/01 10:56

1 概述

CountDownLatch类是Java的j.u.c包中的一个线程同步的辅助类,顾名思义,它可以看成一个倒计时计数器。其主要功能是让某个(或某些)线程等待另外某个(或某些)线程执行结束才接着执行。通过构造函数传入一个指定数值给CountDownLatch对象,通过调用其await方法可以使线程会陷入等待之中,直到这个CountDownLatch对象的countDown方法被调用的次数到达指定的数值为止。需要注意的是,这个CountDownLatch对象的await和countDown方法都可以在一个或多个线程中被调用。

2 典型应用

CountDownLatch类的应用场景通常是某个事件需要等待另外一些事件完成才能接着往下进行。一个典型的应用场景是运动会中的赛跑比赛,起初,各个赛道上的运动员等就位后等待裁判员的发令枪,当发令枪响起后运动员开始起跑,当所有的运动员都达到比赛的终点时,比赛结束。这里,可以抽象出两个CountDownLatch对象,第一个是一个计数为1的CountDownLatch对象,对应于裁判员的发令枪;而第二个是一个计数为运动员数目的CountDownLatch对象,对应于冲刺的终点。描述该场景的代码如下:

import java.util.Random;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by fubinhe on 16/10/27. */public class RaceDemo {    private static final int RUNNER_NUM = 5;    public static void main(String[] args) throws Exception {        CountDownLatch cdlBegin = new CountDownLatch(1);        CountDownLatch cdlArrive = new CountDownLatch(RUNNER_NUM);        ExecutorService exec = Executors.newFixedThreadPool(RUNNER_NUM);        for (int i = 1; i <= RUNNER_NUM; ++i) {            exec.execute(new Runner(i, cdlBegin, cdlArrive));        }        Thread.sleep(2000);        cdlBegin.countDown();        System.out.println("race begins!");        cdlArrive.await();        System.out.println("race ends!");    }}class Runner implements Runnable {    private int id;    private CountDownLatch cdlBegin;    private CountDownLatch cdlArrive;    public Runner(int id, CountDownLatch cdlBegin, CountDownLatch cdlArrive) {        this.id = id;        this.cdlBegin = cdlBegin;        this.cdlArrive = cdlArrive;    }    @Override    public void run() {        try {            System.out.println("runner " + id + " is ready!");            cdlBegin.await();            Thread.sleep(new Random().nextInt(2000));            System.out.println("runner " + id + " has arrived!");            cdlArrive.countDown();        } catch (Exception e) {            e.printStackTrace();        }    }}

上述代码的运行结果如下,可以看到当5个运动员都准备好时(即5个线程都开启了),他们需要等待发令枪(即cdlBegin),而当主线程执行cdlBegin.countDown()后运动员开始起跑。然后主线程需要等待所有的运动员抵达终点才能宣布比赛结束,而所有的运动员抵达终点的标志是他们都执行了cdlArrive.countDown()。

3 源码浅析

3.1 类签名

public class CountDownLatch {}

可以看出CountDownLatch并没有继承任何类(除了Object)和实现任何接口

3.2 内部类

CountDownLatch的主要功能是在其内部维护了一个Sync的内部类对象:private final Sync sync; 如果看过ReentrantLock,可以发现实现方式很类似。

内部类Sync的源代码如下:

/** * Synchronization control For CountDownLatch. * Uses AQS state to represent count. */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;        }    }}

可以看出,Sync类继承自AQS(AbstractQueuedSynchronizer),而CountDownLatch对同步机制的支持主要来自Sync和AQS框架定义的函数。

3.3 主要函数

3.3.1 构造函数

CountDownLatch类的构造函数源代码如下:

/** * Constructs a {@code CountDownLatch} initialized with the given count. * * @param count the number of times {@link #countDown} must be invoked *        before threads can pass through {@link #await} * @throws IllegalArgumentException if {@code count} is negative */public CountDownLatch(int count) {    if (count < 0) throw new IllegalArgumentException("count < 0");    this.sync = new Sync(count);}

可以看出,构造函数通过传一个数值初始化Sync内部类对象,如果继续查看Sync的构造函数的源代码(参考上述Sync的源代码)可以发现其构造函数的主要功能是初始化状态数,而这个状态数是定义在AQS中的。

3.3.2 await函数

await函数将会使调用该函数的线程在锁存器倒计数至零之前一直等待,除非该线程被中断,该函数的源码如下:

public void await() throws InterruptedException {    sync.acquireSharedInterruptibly(1);}
由源码可知,对CountDownLatch对象的await的调用会转发为对Sync的acquireSharedInterruptibly方法的调用,而该方法是从AQS中继承而来的,其源码如下:
public final void acquireSharedInterruptibly(int arg)        throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    if (tryAcquireShared(arg) < 0)        doAcquireSharedInterruptibly(arg);}
可以看出,该方法又调用了CountDownLatch的内部类Sync的tryAcquireShared方法(AQS中的该方法被Sync覆盖)和AQS的doAcquireSharedInterruptibly方法。tryAcquireShared函数的源码如下:
protected int tryAcquireShared(int acquires) {    return (getState() == 0) ? 1 : -1;}
该方法的主要功能只是简单的判断AQS的状态数(即state)是否为0,为0则返回1,不为0则返回-1。doAcquireSharedInterruptibly函数的源码如下:
/** * Acquires in shared interruptible mode. * @param arg the acquire argument */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);    }}

3.3.3 countDown函数

countDown函数将递减计数,如果计数到达零,则释放所有等待的线程,其源代码如下:

public void countDown() {    sync.releaseShared(1);}
其实质是调用AQS的releaseShared方法(Sync内部类没有覆盖),该方法的源代码如下:
/** * 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;}
可以看出,该方法会以共享模式释放对象,并且在函数中会调用到Sync的tryReleaseShared函数(覆盖AQS的),并且根据返回结果可能会调用AQS的doReleaseShared函数,其中,tryReleaseShared源码如下:
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;    }}
而doReleaseShared函数的源代码如下:
/** * 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() {    /*     * 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;    }}

4 总结

通过上述的分析,可以看到CountDownLatch类的底层实现依然是AQS,对其线程所封装的特点是采用共享模式。其实现原理和ReentrantLock相似,也是通过一个继承AQS的Sync内部类实现的,而ReentrantLock里实现了共享模式和独占模式两种,默认情况下调用的是独占模式。






0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 大专文凭查不到学籍该怎么办 学信网上查不到学历怎么办 学信网上没有学历照片怎么办 学信网上没照片怎么办 学历认证报告丢了怎么办 学历认证弄丢了怎么办 手机系统安全证书有问题怎么办 台式电脑的浏览器证书出错怎么办 网上银行k宝密码忘了怎么办 工行证书介质已被锁定怎么办 学历认证是假的怎么办 怕被公司查学历怎么办 淘宝玩具没有怎么办3c 家庭遭遇小三我该怎么办 老公出轨把小三带回家了怎么办 小三怀孕了怎么办准生证 小三怀孕了起诉怎么办 不知情做了小三怎么办 发现自己被三了怎么办 被扇巴掌脸肿了怎么办 分到上海市金鼎学校怎么办 被列入维稳对象怎么办? 资金涉及诈骗案冻结了怎么办 小米浏览器浏览记录找不到了怎么办 米聊账号封了怎么办 管家婆创业版管理员忘记密码怎么办 手机不记得密码了怎么办 手机不记得开锁密码怎么办 oppo手机不记得密码怎么办 电脑密码不记得了怎么办 vivo手机不记得密码了怎么办 运管把车扣了怎么办 大学通选课挂科怎么办 通识必修课挂了怎么办 我想开3d艺术馆怎么办 档案回原籍报到证怎么办 服刑的人孩子上学怎么办 长沙终身教育网用户名忘记了怎么办 乡下卖服装没生意怎么办 没能力没学历该怎么办 没有学历的我该怎么办