併發-CountDownLatch

前言

   最近小鹹兒在看項目代碼時發現有用到CountDownLatch,那這個到底是什麼回事呢?又用來幹什麼呢?讓小鹹兒來一探究竟。


問題

   1、CountDownLatch是什麼?
   2、CountDownLatch有什麼特性?
   3、CountDownLatch適用於什麼場景?
   4、CountDownLatch的初始次數是否可以調整?
   5、CountDownLatch底層是運行什麼實現的?

簡介

CountDownLatch是什麼?

   CountDownLatch是一個計數器閉鎖,通過它可以完成類似阻塞當前線程的功能,即:一個線程或多個線程一直等待,直到其他線程執行的操作完成。
在這裏插入圖片描述

類結構:Sync爲CountDownLatch的內部類

在這裏插入圖片描述

特性

構造函數

/**
 * Constructs a {@code CountDownLatch} initialized with the given count.
 *
 * @param count the number of times {@link #countDown} must be invoked
 *        before threads can pass through {@link #await}
 * @throws IllegalArgumentException if {@code count} is negative
 */
 public CountDownLatch(int count) {
     if (count < 0) throw new IllegalArgumentException("count < 0");
     this.sync = new Sync(count);
 }

// Sync內部類方法,傳入初始次數
   Sync(int count) {
      setState(count);
   }

  通過構造器初始化計數器的值,可實際上是把計數器的值賦值給了AQS的state,也就是用AQS的狀態值來表示計數器值。

  接下來看一下CountDownLatch中幾個重要方法內部是如何調用AQS來實現功能的。

void await()方法

  當前線程調用了CountDownLatch對象的await()方法後,當前線程會被阻塞,直到下面情況之一纔會返回:

  • 當所有的線程都調用了CountDownLatch對象的countDown()方法後,也就是計數器爲0的時候。
  • 其他線程調用了當前線程的interrupt()方法中斷了當前線程,當前線程會拋出InterruptedException
    異常後返回。

  接下來看一下await()方法內部具體是如何實現的?

// CountDownLatch的await()方法
public void await() throws InterruptedException {
	   // 調用AQS的acquireSharedInterruptibly()方法
       sync.acquireSharedInterruptibly(1);
}

  實際上調用的是AQS的acquireSharedInterruptibly()方法

/**
 * Acquires in shared mode, aborting if interrupted.  Implemented
 * by first checking interrupt status, then invoking at least once
 * {@link #tryAcquireShared}, returning on success.  Otherwise the
 * thread is queued, possibly repeatedly blocking and unblocking,
 * invoking {@link #tryAcquireShared} until success or the thread
 * is interrupted.
 * @param arg the acquire argument.
 * This value is conveyed to {@link #tryAcquireShared} but is
 * otherwise uninterpreted and can represent anything
 * you like.
 * @throws InterruptedException if the current thread is interrupted
 */
 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
	 // 如果線程被中斷則拋異常
    if (Thread.interrupted())
        throw new InterruptedException();
       // 嘗試看當前是否計數值爲0,爲0則返回,否則進入AQS的隊列等待
       if (tryAcquireShared(arg) < 0)
          // 繼續阻塞線程
          doAcquireSharedInterruptibly(arg);
 }

  Sync類的tryAcquireShared()方法,內部類:Sync

private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

		// 傳入初始次數
        Sync(int count) {
            setState(count);
        }

		// 獲取還剩的次數
        int getCount() {
            return getState();
        }

		// 嘗試獲取共享鎖
        protected int tryAcquireShared(int acquires) {
        	// 當state爲0時,返回1,表明獲取成功
        	// 當state不爲0時,返回-1,表明需要排隊,即讓當前線程阻塞
            return (getState() == 0) ? 1 : -1;
        }
        
		// 嘗試釋放鎖
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
            	// state得值
                int c = getState();
                // 等於0了,則無法再釋放了
                if (c == 0)
                    return false;
                // 將count的值減1
                int nextc = c-1;
                // CAS原子更新state的值
                if (compareAndSetState(c, nextc))
                	// 減爲0的時候返回true,這時會喚醒後面排隊的線程
                    return nextc == 0;
            }
        }
    }

  await()方法是等待其他線程完成的方法,它會先嚐試獲取一下共享鎖,如果失敗則進入AQS的隊列中排隊等待被喚醒。

    


boolean await(long timeout,TimeUnit unit)方法

  當線程調用了CountDownLatch對象的該方法後,當前線程會被阻塞,直到下面的情況之一發生纔會返回:

  • 當所有線程都調用了CountDownLatch對象的countDown方法後,也就是計時器值爲0的時候,這時候
    返回true。
  • 設置的timeout時間到了,因爲超時而返回false。
  • 其他線程調用了當前線程的interrupt()方法中斷了當前線程,當前線程會拋出InterruptedException
    異常後返回。
public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

  調用AQS中的tryAcquireSharedNanos()方法

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
}

  此方法與await()類似,只不過等待有限。若到達等待時間state值不爲0則直接執行不等待;

void countDown()方法

  當前線程調用該方法後,會遞減計數器的值,遞減後如果計數器爲0則會喚醒所有調用await方法而被阻塞的線程,否則什麼都不做。

  接下來看一下countDown()方法內部具體是如何實現的?

public void countDown() {
	// 調用AQS的釋放共享鎖方法
   sync.releaseShared(1);
}

  調用AQS中的releaseShared()方法

/**
 * Releases in shared mode.  Implemented by unblocking one or more
 * threads if {@link #tryReleaseShared} returns true.
 *
 * @param arg the release argument.  This value is conveyed to
 *        {@link #tryReleaseShared} but is otherwise uninterpreted
 *        and can represent anything you like.
 * @return the value returned from {@link #tryReleaseShared}
 */
 public final boolean releaseShared(int arg) {
    // 嘗試釋放共享鎖,如果成功了就喚醒排隊的線程
    if (tryReleaseShared(arg)) {
        // AQS的釋放資源方法
        doReleaseShared();
        return true;
    }
    return false;
 }

  可以看出來CountDownLatch的countDown()方法是委託了sync調用了AQS的releaseShared方法,後者調用了sync實現的AQS的tryReleaseShared()方法,具體代碼如下:CountDownLatch類中sync內的方法。

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    // 循環進行CAS,直到當前線程成功完成CAS使計數值(狀態值state)減1並更新到state
    for (;;) {
         int c = getState();
         // 如果當前狀態值爲0則直接返回
         if (c == 0)
             return false;
         // CAS設置計數值減1
         int nextc = c-1;
         // 通過原子操作把改變後的state值寫入內存中
         if (compareAndSetState(c, nextc))
             return nextc == 0;
    }
}

  countDown()方法,會釋放一個共享鎖,也就是count的次數會減1。首先會獲取當前狀態值(計數器值),如果當前狀態值爲0則直接返回false,則countDown()方法直接返回;否則使用CAS設置計數器減1,CAS失敗則循環重試,否則如果當前計數器爲0則返回true。返回true後,說明當前線程爲最後一個調用countDown()方法的線程,那麼該線程除了讓計數器減1外,還需要喚醒調用CountDownLatch的await()方法而被阻塞的其他線程。

具體實例

  CountDownLatch 等待遊戲玩家準備完成後,主線程開始遊戲

package concurrent;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample2 {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(4);
        for (int i = 0; i < latch.getCount(); i ++){
            new Thread(new MyThread(latch),"player"+i).start();
        }
        System.out.println("正在等待所有玩家準備好");
        latch.await();
        System.out.println("開始遊戲");
    }

    private static class MyThread implements Runnable{
        private CountDownLatch latch;

        public MyThread(CountDownLatch latch){
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                Random rand = new Random();
                // 產生1000到3000之間的隨機整數
                int randomNum = rand.nextInt((3000 - 1000) + 1) + 1000;
                Thread.sleep(randomNum);
                System.out.println(Thread.currentThread().getName() + "已經準備好了,所是使用的時間爲 " + ((double) randomNum / 1000) + "s");
                latch.countDown();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

CountDownLatch的初始次數是否可以調整?

  不能,它沒有提供修改(增加或者減少)次數的方法,除非利用反射作弊。

CountDownLatch底層是利用什麼實現的?

  AQS和CAS(具體在上述源碼解析中有體現)


黑色背景

小結

  • CountDownLatch表示允許一個或多個線程等待其他線程的操作執行完畢後再執行後續的操作。
  • CountDownLatch使用AQS的共享鎖機制實現。
  • CountDownLatch初始化的時候需要傳入次數count。
  • 每次調用countDown()方法,count的次數減1。
  • 每次調用await()方法的時候會嘗試獲取鎖,這裏的獲取鎖其實是檢查AQS的state變量的值是否爲0。
  • 當state(count)的值減爲0時會喚醒排隊着的線程。

  除了CountDownLatch之外,還有CyclicBarrier和Semaphore等待着小鹹兒去學習和總結。

參考文章:Java併發編程筆記之CountDownLatch閉鎖的源碼分析

     死磕Java同步系列之CountDownLatch源碼解析

感謝您的閱讀~~

發佈了180 篇原創文章 · 獲贊 106 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章