CountDownLatch

CountDownLatch

CountDownLatch 直译过来为“倒数阀门”,顾名思义,它是起到一个阀门的作用,实际上是用来控制线程的执行过程。来看一下这个类提供了哪些方法:

CountDownLatch-Outline

从图中可以看出,CountDownLatch 的构造方法是传入一个整数,然后提供了 await()countDown()getCount() 等方法,还有一个内部类 Sync,这个内部类继承了 AbstractQueuedSynchronizer
整个 CountDownLatch 的核心是 Sync 类,先来看这个类。

Sync

private static final class Sync extends AbstractQueuedSynchronizer { // 继承了 AQS 类
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) { // 构造函数,将 AQS 中的 state 设置为 count 的值
        setState(count);
    }

    int getCount() { // 获取 state 的值
        return getState();
    }
    /**
     * 重写 AQS 中的 tryAcquireShared
     * 尝试获取共享锁,如果当前 state 等于 0 返回 1 表示获取成功,否则返回 -1 表示失败
     */
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }
    /**
     * 重写 AQS 中的 tryReleaseShared
     */
    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) { // 死循环,保证 cas 更改成功
            int c = getState();
            if (c == 0) // 如果当前 state 为 0,释放失败
                return false;
            int nextc = c-1; // state - 1
            if (compareAndSetState(c, nextc)) // 更改最新的 state 值
                return nextc == 0; // 如果 state - 1 后为 0 返回 true,否则返回 false
        }
    }
}

Sync 类重写了 AQS 中的 tryAcquireSharedtryReleaseShared 方法,tryAcquireShared 判断当前的 state 是否为 0,tryReleaseShared 中对 state 进行减一操作,如果减过之后 state 为 0 则返回 true。
CountDownLatch 中的方法都是调用了 Sync 中的方法,接下来看一下 CountDownLatch 的实现。

CountDownLatch(int)

// 构造方法,初始化 sync
public CountDownLatch(int count) {
    // 如果 count 小于 0 就抛出非法参数异常
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

await

await 有两种,一种是没有时间限制的,一种是有时间限制的。

/**
 * 调用 AQS 的 acquireSharedInterruptibly
 * 内部还是调用了 tryAcquireShared 方法,也就是判断当前 state 是否为 0
 * 如果不为 0 那么当前线程加入等待队列,被阻塞
 */
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1); 
}
/**
 * 调用 AQS 的 tryAcquireSharedNanos
 * 在指定时间内没有获取锁,返回 false
 */
public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

countDown

/**
 * 调用 AQS 的 releaseShared
 * releaseShared 内部调用了 Sync 中重写的 tryReleaseShared 方法,将 state - 1
 * 如果 state - 1 后为 0 ,返回 true,那么就会唤醒等待的线程
 */
public void countDown() {
    sync.releaseShared(1);
}

总结

根据对 AbstractQueuedSynchronizer 的了解,加上看完上面的源码,我们已经清楚地了解了 CountDownLatch 的作用以及原理:

  • CountDownLatch 中的内部类 Sync 继承了 AQS,并重写了 tryAcquireSharedtryReleaseShared 方法
  • CountDownLatch 实现了对 AQS 共享锁相关方法的封装,相当于换了个直观的名字
  • 执行 await 方法判断 state,相当于计数器,是否为 0,如果不为 0,当前线程执行到这里以后阻塞,直到 state 为 0
  • 执行 countDown 方法对 state 进行减一操作,如果减一后为 0 ,那么唤醒等待的线程

简单归纳一下就是,CountDownLatch 是阀门,初始化时设置的 count 表示容量,线程执行 await 方法等待阀门内没有东西(state == 0),如果阀门内有东西(state > 0),那么执行 await 的方法被阻塞,直到阀门内没有东西 (state == 0)才被唤醒。每当一个线程执行一次 countDown 方法,阀门内的东西就减少一个 (state = state - 1),如果减少一个以后阀门内没有东西了,就唤醒等待阀门为空的线程。
更生动一点来说,打个比方,有一个公共澡堂,澡堂内有一定数量的淋浴头(count),这些淋浴头都已经有人在使用了,每当有人想要洗澡的时候,这些人都要等待所有的洗澡的人都已经洗完了(state == 0),因此他们要在外面等待(await),每当有一个人洗完了(countDown),都会看一下是不是所有人都洗完了,如果所有人都洗完了,那么给外面等待的人说,你们可以进来了,这时候等待的人才能进来(也就是线程被唤醒,继续执行)。

应用 Demo

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {

    private static final int numOfThreads = 10; // 线程数

    private static final int sleepTime = 3000; // 睡眠时间

    public static void main(String[] args) {
        CountDownLatch startLatch = new CountDownLatch(1); // 只有执行 start.countDown() 之后线程才开始执行
        CountDownLatch threadLatch = new CountDownLatch(numOfThreads);

        for (int i = 0; i < numOfThreads; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        startLatch.await(); // 等待 startLatch.countDown 才开始执行
                        Thread.sleep(sleepTime);
                        System.out.println(Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    threadLatch.countDown(); // 完成的线程数 -1
                }
            }).start();
        }

        startLatch.countDown(); // 所有线程开始执行
        long start = System.currentTimeMillis();
        try {
            threadLatch.await(); // 等待所有线程执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(numOfThreads + " 个线程执行完花费时间为 : " + (System.currentTimeMillis() - start) + " ms");
    }

}

执行结果为:

Thread-8
Thread-9
Thread-5
Thread-3
Thread-1
Thread-0
Thread-2
Thread-4
Thread-7
Thread-6
10 个线程执行完花费时间为 : 3003 ms
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容