• 多线程编程-- part 7 CountDownLatch


    CountDownLatch简介

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

    CountDownLatch和CyclicBarrier的区别
    (01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
    (02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

    CountDownLatch函数列表

    CountDownLatch(int count)
    构造一个用给定计数初始化的 CountDownLatch。
    
    // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
    void await()
    // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
    boolean await(long timeout, TimeUnit unit)
    // 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
    void countDown()
    // 返回当前计数。
    long getCount()
    // 返回标识此锁存器及其状态的字符串。
    String toString()

    学习CountDownLatch的核心函数

    1.CountDownLatch(int count)

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    说明:该函数是创建一个Sync对象,而Sync是继承于AQS类。Sync构造函数如下:

    Sync(int count) {
        setState(count);
    }

    setState()在AQS中实现,源码如下:

    protected final void setState(long newState) {
        state = newState;
    }

    说明:在AQS中,state是一个private volatile long类型的对象。对于CountDownLatch而言,state表示的”锁计数器“。CountDownLatch中的getCount()最终是调用AQS中的getState(),返回的state对象,即”锁计数器“。

     2.await()

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

    说明:该函数实际上是调用的AQS的acquireSharedInterruptibly(1);

    AQS中的acquireSharedInterruptibly()的源码如下:

    复制代码
    public final void acquireSharedInterruptibly(long arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
    复制代码

    说明:acquireSharedInterruptibly()的作用是获取共享锁。
    如果当前线程是中断状态,则抛出异常InterruptedException。否则,调用tryAcquireShared(arg)尝试获取共享锁;尝试成功则返回,否则就调用doAcquireSharedInterruptibly()。doAcquireSharedInterruptibly()会使当前线程一直等待,直到当前线程获取到共享锁(或被中断)才返回。

    tryAcquireShared()在CountDownLatch.java中被重写,它的源码如下:

    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    说明:tryAcquireShared()的作用是尝试获取共享锁。
    如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。

    3.countDown()

      

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

    说明:该函数实际上调用releaseShared(1)释放共享锁。

    releaseShared()在AQS中实现,源码如下:

    复制代码
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    复制代码

    说明:releaseShared()的目的是让当前线程释放它所持有的共享锁。
    它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。

    tryReleaseShared()在CountDownLatch.java中被重写,源码如下:

    复制代码
    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            // 获取“锁计数器”的状态
            int c = getState();
            if (c == 0)
                return false;
            // “锁计数器”-1
            int nextc = c-1;
            // 通过CAS函数进行赋值。
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
    复制代码

    说明:tryReleaseShared()的作用是释放共享锁,将“锁计数器”的值-1。

    CountDownLatch实例

    public class testHello {
    
        private static int LATCH_SIZE = 5;
        private static CountDownLatch doneSignal;
        public static void main(String[] args) {
    
            try {
                doneSignal = new CountDownLatch(LATCH_SIZE);
    
                // 新建5个任务
                for(int i=0; i<LATCH_SIZE; i++)
                    new InnerThread().start();
    
                System.out.println("main await begin.");
                // "主线程"等待线程池中5个任务的完成
                doneSignal.await();
    
                System.out.println("main await finished.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        static class InnerThread extends Thread{
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");
                    // 将CountDownLatch的数值减1
                    doneSignal.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

      可以看出,主线程通过doneSignal.await()等待其他线程将doneSignal递减至0,其他的几个子线程,每次通过countDown函数将doneSignal值减1。当doneSignal为0时,main被唤醒继续执行。

  • 相关阅读:
    小程序中自定义组件
    rem是如何实现自适应布局的?
    基于vue前端状态管理模式
    vue项目使用keep-alive的作用
    JS移动元素的方法
    es6 promise then对异常处理的方法
    async/await 中await接收的promise的问题
    angularjs ngRoute demo
    angularjs $watch demo
    Html5 Geolocation demo
  • 原文地址:https://www.cnblogs.com/jijiji/p/6929423.html
Copyright © 2020-2023  润新知