面試遇到CountDownLatch有這一篇就夠啦

前言

日常開發中多線程是我們經常用到了一種技術手段,通過合理的使用多線程可以極大的提高程序中的處理能力,但是在使用多線程的過程中,我們一定需要特別關注多個線程在處理過程中對後續任務的影響,在處理那些涉及多線程任務和單線程任務需要順序處理的時候,我們就可以通過CountDownLatch來進行控制,接下來我們就來深入瞭解一下CountDownLatch的原理和用法。

源碼分析

  • 靜態類:Sync
    初始化CountDownLatch便是創建了一個私有的靜態類Sync,Sync繼承了抽象類AbstractQueuedSynchronizer,初始化的過程主要是對標誌量state進行賦值。在Sync中重寫了tryAcquireShared、tryReleaseShared
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) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
  • countDown:
    該方法是對我們賦值的標誌量進行減1的操作,該線程是一個線程安全的操作,通過自旋來獲取標誌量state,並對state進行線程安全的uncafe.compareAndSwapInt()操作
public void countDown() {
        sync.releaseShared(1);
    }
  • await:
    該方法主要是用來判斷上方賦值的標誌量state是否已被減爲0,當state爲0時表示所有線程均執行完成,否則進行自旋狀態來不斷嘗試判斷是否爲0,await方法還有一個重載方法,可以設置等待的最長時間,如果直到最大等待時間state還不爲0,則退出自旋繼續執行下面的代碼。
public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }    
  • getCount:
    獲取標誌量state當前的值
public void countDown() {
        sync.releaseShared(1);
    }

使用場景

A、B兩個任務,A任務通過多線程來進行處理,只有A任務的全部線程都執行完成了纔可以執行B任務,這個時候就可以通過給A任務中加一個公用的CountDownLatch。
在這裏插入圖片描述

使用步驟

  • 1 創建CountDownLatch實例,初始化state的值
  • 2 創建線程池,在線程池中新建線程,初始化線程中傳入CountDownLatch實例
  • 3 在新建線程中處理業務代碼,處理完成則調用countDown方法來對CountDownLatch實例中的state的標誌量減1
  • 4 調用CountDownLatch實例中的await方法,如果沒有設置超時時間,則將阻塞當前線程直到標誌量state = 0爲止;如果設置了await方法的超時時間,在超時時間內標誌量state被減爲0則退出線程阻塞,在超時時間內標誌量state未被減爲0,到了超時時間也會退出線程阻塞
  • 5 接下來將執行await方法後的邏輯

主線程

public class TestCountDownLatch {

  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    CountDownLatch countDownLatch = new CountDownLatch(10);
    for (int i = 0; i < 10; i++) {
      executorService.execute(new HaveCountDownLatchThread(countDownLatch, i, new Random()));
    }
    try {
      countDownLatch.await();

      System.out.println("查詢當前狀態量state : " +countDownLatch.getCount() + "; 前面的線程已經全部執行完成,可以接着衝啦。。。");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }


  }
}

任務線程

public class HaveCountDownLatchThread implements Runnable{
  CountDownLatch countDownLatch;
  int i;
  Random random;

  HaveCountDownLatchThread(CountDownLatch countDownLatch, int i, Random random) {
    this.countDownLatch = countDownLatch;
    this.i = i;
    this.random = random;
  }
  @Override
  public void run() {
    int sleep = random.nextInt(1000);
    System.out.println("第【" + i + "】個啓動的線程即將睡眠 " + sleep + "毫秒");
    try {
      Thread.sleep(sleep);
      System.out.println("第【" + i + "】個啓動的線程已睡醒,查詢當前的state狀態量:" + countDownLatch.getCount());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("第【" + i + "】個啓動的線程已睡醒,即將執行countDown方法");
    countDownLatch.countDown();

  }
}

總結

CountDownLatch是Java中concurrent包中的一個工具類,CountDownLatch可以看作是一個計數器,這個計數器的操作是原子操作:同一時刻只能有一個線程去操作這個計數器,我們初始化CountDownLatch的時候設置一個計數值,計數器中的計數值被線程減爲0之前,任何調用CountDownLatch對象上的await方法都會被阻塞,任何線程調用CountDownLatch對象中的countDown方法則爲計數器減1。一直減到state==0或者超時後,開始執行計數器的await方法後的代碼,所以如果我們將CountDownLatch的源碼查看一下,你會發現,CountDownLatch並沒有那麼複雜,加油!

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