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
仅用作调试

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。