1. 程式人生 > >JUC鎖——CountDownLatch

JUC鎖——CountDownLatch

一、什麼是CountDownLatch

CountDownLatch 類位於java.util.concurrent包下,利用它可以實現類似計數器的功能。比如有一個任務A,它要等待其他2個任務執行完畢之後才能執行,此時就可以利用CountDownLatch來實現這種功能了。CountDownLatch是通過一個計數器來實現的,計數器的初始值為執行緒的數量。每當一個執行緒完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的執行緒已經完成了任務,然後在閉鎖上等待的執行緒就可以恢復執行任務。

方法列表:

CountDownLatch(int count)
構造一個用給定計數初始化的 CountDownLatch。

// 使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷。
void await()
// 使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷或超出了指定的等待時間。
boolean await(long timeout, TimeUnit unit)
// 遞減鎖存器的計數,如果計數到達零,則釋放所有等待的執行緒。
void countDown()
// 返回當前計數。
long getCount()
// 返回標識此鎖存器及其狀態的字串。
String toString()

二、CountDownLatch資料結構

CountDownLatch通過共享鎖實現。它包含了sync物件,sync是Sync型別。Sync是例項類,它繼承於AQS。

三、原始碼分析

3.1 CountDownLatch(int count)

該方法主要就是建立一個Sync物件,Sync是一個內部類,繼承於AQS:

建立Sync物件時,建構函式中的setState方法在AQS內部實現:

在AQS中,state是一個private volatile long型別的物件。

對於CountDownLatch而言,state表示的”鎖計數器“。CountDownLatch中的getCount()最終是呼叫AQS中的getState(),返回的state物件,即”鎖計數器“。

3.2 await()

該方法實際上是呼叫的AQS的acquireSharedInterruptibly(1);

acquireSharedInterruptibly()的作用是獲取共享鎖。如果當前執行緒是中斷狀態,則丟擲異常InterruptedException。否則,呼叫tryAcquireShared(arg)嘗試獲取共享鎖;嘗試成功則返回,否則就呼叫doAcquireSharedInterruptibly()。doAcquireSharedInterruptibly()會使當前執行緒一直等待,直到當前執行緒獲取到共享鎖(或被中斷)才返回。

tryAcquireShared()在CountDownLatch.java中被重寫

tryAcquireShared()的作用是嘗試獲取共享鎖。如果"鎖計數器=0",即鎖是可獲取狀態,則返回1;否則,鎖是不可獲取狀態,則返回-1。

doAcquireSharedInterruptibly(需要補充)

3.3 countDown()

該函式實際上呼叫releaseShared(1)釋放共享鎖。

releaseShared在AQS中的實現:

releaseShared()的目的是讓當前執行緒釋放它所持有的共享鎖。它首先會通過tryReleaseShared()去嘗試釋放共享鎖。嘗試成功(返回false),則直接返回;嘗試失敗(返回true,最後一個執行緒執行counrDown),則通過doReleaseShared()去釋放共享鎖,“主執行緒”就可以繼續執行了。

tryReleaseShared()在CountDownLatch.java中被重寫:

如上程式碼可以看到首先獲取當前狀態值(計數器值),如果當前狀態值為 0 則直接返回 false ,則countDown()方法直接返回;否則執行程式碼使CAS設定計數器減一,CAS失敗則迴圈重試,否則如果當前計數器為 0 則返回 true 。返回 true 後,說明當前執行緒是最後一個呼叫countDown()方法的執行緒,那麼該執行緒除了讓計數器減一外,還需要喚醒呼叫CountDownLatch的await 方法而被阻塞的執行緒。之所以使用第一個if進行判斷, 是為了防止計數器值為 0 後,其他執行緒又呼叫了countDown方法,狀態值就會變成負數。

四、CountDownLatch的使用示例

主執行緒等待兩個子執行緒執行完畢後再執行:

package com.juc.countdownlatch;

import java.util.concurrent.CountDownLatch;

/**
 * @Author: 98050
 * @Time: 2018-12-20 20:50
 * @Feature: CountDownLatch的使用
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(2);

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+",子執行緒開始執行");
                /**
                 * 計數器的值減一
                 */
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName()+",子執行緒執行結束");
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+",子執行緒開始執行");
                /**
                 * 計數器的值減一
                 */
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName()+",子執行緒執行結束");
            }
        }).start();
        /**
         * 計數器值為0,恢復任務繼續執行
         */
        countDownLatch.await();

        System.out.println("兩個子執行緒執行完畢,主執行緒繼續執行");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
        }
    }
}

五、應用場景

1、其它的一些執行緒需要某個執行緒做準備工作。例如:資料庫的連線等。

2、某個執行緒需要等待一些執行緒工作完之後清理資源。斷開資料庫的連線等。