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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章