CountDownLatch源码阅读


参考:用CountDownLatch提升请求处理速度,CountDownLatch的理解和使用

1.使用场景

        看了网上的一些解释,这边直接摘取一下过来:
在这里插入图片描述
在这里插入图片描述

2.代码示例

        代码使用的实例:

package huangzj.springmvc.controller;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    private CountDownLatch countDownLatch;

    private int start = 10;
    private int mid = 100;
    private int end = 200;

    private volatile int tmpRes1, tmpRes2;

    private int add(int start, int end) {
        int sum = 0;
        for (int i = start; i <= end; i++) {
            sum += i;
        }
        return sum;
    }


    private int sum(int a, int b) {
        return a + b;
    }

    public void calculate() {
        countDownLatch = new CountDownLatch(2);

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 确保线程3先与1,2执行,由于countDownLatch计数不为0而阻塞
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + " : 开始执行");
                    tmpRes1 = CountDownLatchDemo.this.add(start, mid);
                    System.out.println(Thread.currentThread().getName() +
                        " : calculate ans: " + tmpRes1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }
        }, "线程1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 确保线程3先与1,2执行,由于countDownLatch计数不为0而阻塞
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + " : 开始执行");
                    tmpRes2 = CountDownLatchDemo.this.add(mid + 1, end);
                    System.out.println(Thread.currentThread().getName() +
                        " : calculate ans: " + tmpRes2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }
        }, "线程2");


        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + " : 开始执行");
                    countDownLatch.await();
                    int ans = CountDownLatchDemo.this.sum(tmpRes1, tmpRes2);
                    System.out.println(Thread.currentThread().getName() +
                        " : calculate ans: " + ans);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程3");

        thread3.start();
        thread1.start();
        thread2.start();
    }


    public static void main(String[] args) throws InterruptedException {
        CountDownLatchDemo demo = new CountDownLatchDemo();
        demo.calculate();

        Thread.sleep(1000);
    }
}


        结果如下,在await之前的线程运行结束之后才会执行后面的线程:
在这里插入图片描述

3.代码阅读

1.构造方法

        构造方法的入参表示,需要执行多少次线程之后,才进行释放,后面的线程才能运行

    public CountDownLatch(int count) {
        //这边的入参解释,count表示执行一定次数之后线程才能放过.
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

2.countDown

        一个countDown表示一次共享锁的释放:

    public void countDown() {
        //释放一次锁.如果数量为0则表示等待的线程都执行完毕了
        sync.releaseShared(1);
    }

releaseShared是AQS提供的。

    public final boolean releaseShared(int arg) {
    	//如果尝试释放锁成功
        if (tryReleaseShared(arg)) {
        	//释放锁,修改参数
            doReleaseShared();
            return true;
        }
        return false;
    }

        我们主要来看一下tryReleaseShared这个方法是怎么释放的
        如果状态本来就是0说明不需要释放,直接返回false,否则通过释放state成功返回true,则进入上面的doReleaseShared修改其他线程的waitState,告知锁释放.

        /**
         * 这边的实现就是死循环通过CAS的方式去释放state
         */
        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;
                }
            }
        }

3.await

        await方法调用会造成当state不等于0其他线程进行等待,看这边的注释
在这里插入图片描述

    public void await() throws InterruptedException {
        //造成当前线程等待countDown计数降为0,除非线程本来就是中断状态
        sync.acquireSharedInterruptibly(1);
    }

        这边如果线程是中断状态,再进行中断直接报错.

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //如果线程被中断报错
        if (Thread.interrupted()){
            throw new InterruptedException();
        }
        //尝试获取共享锁
        if (tryAcquireShared(arg) < 0){
		  doAcquireSharedInterruptibly(arg);
 		}
           
    }

        tryAcquireShared这边判断是否获取锁成功的条件就是state是不是为0,这和我们上面说的一致,当countDown没有降到0的时候,其他线程是不能进行操作的。

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

        这边其实我们看到会往队列里面加一个共享节点,然后进入等待获取锁,直到获取成功为止。

 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);
                        // help GC
                        p.next = null;
                        failed = false;
                        return;
                    }
                }
				
				//进入队列之后进行休眠,park,休眠之后如果判断线程状态是intercept,直接抛错
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
                    throw new InterruptedException();
                }
            }
        } finally {
            if (failed){
                cancelAcquire(node);
            }
        }
    }

4.我的理解

        CountDownLatch之所以可以实现在await之前的线程执行结束才执行后面的线程,我的想法的线程是有在队列不在队列的区分的,线程调用countDown方法表示该线程一定是在await之前去执行的。而调用await方法的线程一定是在countDown直到降为0才能进行运行的。
        举个例子就是说:现在有线程A、B、C、D,AB运行结束之后产生结果,CD才能使用他们带来的结果进行计算。则在AB的运行结束之后需要释放计数器(-1操作)就是调用countDown方法。
        而CD需要进行等待,在CD一开始就应该调用await方法,进入队列里面去等待,这样才能保证在AB运行的期间,CD不会去抢占线程获取到AB的运行权限。
我们用一张图来解释这个过程:
在这里插入图片描述

发布了84 篇原创文章 · 获赞 15 · 访问量 3219
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章