JAVA并发之CountDownLatch解读

CountDownLatch被大哥们形象化称为门栓,老规矩我对这个类的理解是这个类其实是一个倒计数器,举例说明一个使用场景
工厂老板收到了一个订单是生产一辆车,尊贵的老板肯定是不自己亲自干了,找来了三个工人,A工人负责生产轮胎,B工人负责生产门子,C工人负责生产门把手(这他么太简单了吧?)剧情需要一会可以给大伙讲这里面的细节
吸取昨天朋友们的建议写个小的示例代码

/**
 * @author guozc
 *
 * 2020年6月30日
 */
public class CountDownLatchTest {
    
    public static void main(String[] args) throws InterruptedException {
        //三个倒计数的阻止器
        CountDownLatch countDownLatch = new CountDownLatch(3);
        //生产门子的开始干活
        Thread makeMen = new Thread(new MakeMen(countDownLatch));
        makeMen.start();
        //生产轮子的开始干活
        Thread makeLun = new Thread(new MakeLun(countDownLatch));
        makeLun.start();
        //生产把手的开始干活,不仅活少干的还晚
        Thread makeBashou = new Thread(new MakeBashou(countDownLatch));
        makeBashou.start();
        //老板在此等着
        countDownLatch.await();
        System.out.println("组装结束");
    }
}

门子生产线程

/**
 * @author guozc
 *
 * 2020年6月30日
 */
public class MakeMen implements Runnable {
    private CountDownLatch lock;
    public MakeMen(CountDownLatch lock) {
       this.lock = lock;
    }
    /**
     * @author guozc
     *
     * 2020年6月30日
     */
    @Override
    public void run() {
        try {
            
            System.out.println("生产门子中");
            //假设生产门子需要五秒
            Thread.sleep(5000);
            //为了方便看时间
            System.out.println("门子组装完成"+ new Date());
            lock.countDown();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    
}

轮胎生产线程

/**
 * @author guozc
 *
 * 2020年6月30日
 */
public class MakeLun implements Runnable {
    private CountDownLatch lock;
    public MakeLun(CountDownLatch lock) {
       this.lock = lock;
    }
    /**
     * @author guozc
     *
     * 2020年6月30日
     */
    @Override
    public void run() {
        try {
            
            System.out.println("生产轮子中");
            //假设轮子生产需要五秒
            Thread.sleep(5000);
            //方便看时间
            System.out.println("轮子组装完成"+ new Date());
            lock.countDown();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    
}

把手生产线程那个动身最晚,活最少的线程

/**
 * @author guozc
 *
 * 2020年6月30日
 */
public class MakeBashou implements Runnable {
    private CountDownLatch lock;
    public MakeBashou(CountDownLatch lock) {
       this.lock = lock;
    }
    /**
     * @author guozc
     *
     * 2020年6月30日
     */
    @Override
    public void run() {
        try {
            
            System.out.println("生产把手中");
            //一秒就生产完了
            Thread.sleep(1000);
            System.out.println("把手生产完成" + new Date());
            lock.countDown();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
}

执行CountDownLatchTest main方法启动工作最后输出结果如下

生产门子中
生产轮子中
生产把手中
把手生产完成Tue Jun 30 19:26:13 CST 2020
轮子组装完成Tue Jun 30 19:26:17 CST 2020
门子组装完成Tue Jun 30 19:26:17 CST 2020
组装结束


可以看到把手很快就执行完了but组装并没有结束,所以CountDownLatch一个特点就是遵从木桶原理,整个任务的最终时间往往被时间最长的任务决定。

这里只是一个简单的小例子,实际中使用多数是要结合线程池进行调用,做到资源的保护避免疯狂创建过多分任务线程去执行,干趴系统资源

源码版本来自jdk1.8

下面简单撸一下源码,打开后映入眼帘的依然是那个熟悉的Sync类

private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
        ##初始化任务数,或者说倒计数的数量,交给state变量保存
        Sync(int count) {
            setState(count);
        }
        ##返回目前state的值,其实也是剩余的正在执行的任务数
        int getCount() {
            return getState();
        }
        ##告诉给aqs,state是否是为0,如果不是那么当老板await的时候则让主线程park
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        ##通过循环去减小state的值,子线程的countDown()方法使用
        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;
            }
        }
    }

下面是构造方法没啥可说的

public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
通过初始化sync里面state数量进行倒计时数字初始化

下面是await方法,一般是在主线程里面使用

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
追一下 acquireSharedInterruptibly(1)代码来到了 AQS里面
 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
再追一下 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);
        }
    }
主要作用检查竞争状态,如果竞争状态为0那么就把当前线程做对应的node设置为head并放行
否则就park,小马扎,小茶水伺候。等着通过realese时候后进行唤醒

await(long timeout, TimeUnit unit)为等待设置一个过期时间

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

public void countDown() 方法主要在执行任务线程中调用,当任务执行完减少倒计数

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

经过一番追踪最后会到执行到
CountDownLatch Sync类里面的
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;
            }
        }
逻辑很简单就是修改倒计数数字,如果倒计数数字为0了,那么就会执行AQS里面的
 public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            ##竞争状态state为0了,也就是员工做做都干完了
            ##唤醒阻塞在队里面的node,其实就是刚才阻塞的老板那个主线程
            doReleaseShared();
            return true;
        }
        return false;
    }

最后一个方法

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

返回这一时刻的任务剩余量,还是那个问题不是很准确,因为在读的瞬间可能state值会被其他线程减小。注释也说的很清楚
this method is typically used for debugging and testing purposes
仅用作调试

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