主线程等待子线程执行结束再执行—— CountDownLatch

一、一个例子

private static void countDownLatchTest(List<String> roles){
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i=0;i<10;i++){
CountRunnable countRunnable = new CountRunnable(i+"",i+"",roles,countDownLatch);
new Thread(countRunnable).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public class CountRunnable implements Runnable{

private String id;
private String name;
private List<String> roles;
private CountDownLatch countDownLatch;
private String password;

public CountRunnable(String id, String name, List<String> roles,
                     CountDownLatch countDownLatch){
    this.name = name;
    this.id = id;
    this.roles = roles;
    this.countDownLatch = countDownLatch;
}

@Override
public void run() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    roles.add(name);
    System.out.println("执行了-id:"+id+"name:"+name+"roles:"+roles.size());
    try {
        countDownLatch.countDown();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public List<String> getRoles() {
    return roles;
}

public void setRoles(List<String> roles) {
    this.roles = roles;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

}
输出结果:

开始:1515333020630
执行了-id:0name:0roles:1
执行了-id:1name:1roles:2
执行了-id:4name:4roles:4
执行了-id:8name:8roles:7
执行了-id:3name:3roles:9
执行了-id:7name:7roles:10
执行了-id:9name:9roles:8
执行了-id:6name:6roles:6
执行了-id:5name:5roles:5
执行了-id:2name:2roles:4
[0, 1, 2, 4, 5, 6, 8, 9, 3, 7]
结束:1515333021639耗时:1009roles size:10
可以看出,上面每个线程执行1000ms,总时长1009ms.

二、源码介绍

/**

  • 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);
    }
    这里看清楚一点:构造器中要指定大于0的参数。

然后创建了一个sync对象,这个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类的内部类,是CountDownLatch的核心实现。在内部类里有几个方法:

getCount:获取当前等待的线程数
tryAcquireShared:如果线程数为0才返回1,即当前没有等待的线程
tryReleaseShared:重写AQS方法,实现每被countDown调用,就将标志位-1,直到标志位为0。
而CountDownLatch的里的几个方法:

public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

public void countDown() {
sync.releaseShared(1);
}

public long getCount() {
return sync.getCount();
}
acquireSharedInterruptibly:

public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//判断是否发生中断
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//注意:-1表示获取到了共享锁,1表示没有获取共享锁
doAcquireSharedInterruptibly(arg);
}
继续深入doAcquireSharedInterruptibly,这段我是看不明白了:

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);//就判断尝试获取锁
/*
这里要注意一下r的值就2种情况-1和1:
情况1.r为-1,latch没有调用countDown(),state是没有变化的导致state一直大于0或者调用了countDown(),但是state不等于0,直接在for循环中等待
情况2.r为1,证明countDown(),已经减到0,当前线程还在队列中,state已经等于0了.接下来就是唤醒队列中的节点
*/
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);
}
}
setHeadAndPropagate:

private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // 记录头结点
setHead(node);//设置node为头结点
/*
*从上面传递过来 propagate = 1;
*一定会进入下面的判断
*/
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;//获得当前节点的下一个节点,如果为最后一个节点或者,为shared
if (s == null || s.isShared())
doReleaseShared();//释放共享锁
}
}
isShared:

final boolean isShared() {
return nextWaiter == SHARED;
}
释放共享锁,通知后面的节点:

private void doReleaseShared() {
for (;;) {
Node h = head;//获得头结点
if (h != null && h != tail) {
int ws = h.waitStatus;//获取头结点的状态默认值为0
if (ws == Node.SIGNAL) {如果等于SIGNAL唤醒状态
//将头结点的状态置成0,并使用Node.SIGNAL(-1)与0比较,continue,h的状态设置为0,不会再进入if (ws == Node.SIGNAL)
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}//判断ws是否为0,并且h的状态不等于0,这里是个坑啊,ws等于0,h就是0啊,所以if进不来的,并设置节点为PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
如果头结点等于h,其实也没有一直loop,由于上面写的Node h = head,就算前面的条件都不满足,这里一定会break
if (h == head) // loop if head changed
break;
}
}
深入到这里,以超出我的想象力。

三、CountDownLatch总结

CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count个线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行。


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

推荐阅读更多精彩内容

  • 此篇博客所有源码均来自JDK 1.8 在上篇博客中介绍了Java四大并发工具之一的CyclicBarrier,今天...
    chenssy阅读 2,919评论 2 27
  • CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的...
    charming_coder阅读 96,717评论 24 86
  • Countdownlatch 此小节介绍几个与锁有关的有用工具。 闭锁(Latch) 闭锁(Latch):一种同步...
    raincoffee阅读 324评论 0 1
  • 错过的是什么?流逝的是什么?遇见的是什么?离开你是我伤的最深的最无助的一次,我想可是我什么都做不了了,你想拥有一个...
    BIU特FAO卡哇伊阅读 440评论 0 0
  • 四方之城(2)上一章 这天吴忧宁静的瑜伽时光被好几个电话打断,先是爸爸,接着是程城的妹妹程蕙,然后是弟弟吴愁,都是...
    今年九十岁阅读 672评论 3 5