JUC——執行緒同步輔助工具類(Semaphore,CountDownLatch,CyclicBarrier)

  • 2019 年 10 月 8 日
  • 筆記

CountDownLatch

CountDownLatch是一個計數器閉鎖,通過它可以完成類似於阻塞當前執行緒的功能,即:一個執行緒或多個執行緒一直等待,直到其他執行緒執行的操作完成。CountDownLatch用一個給定的計數器來初始化,該計數器的操作是原子操作,即同時只能有一個執行緒去操作該計數器。調用該類await方法的執行緒會一直處於阻塞狀態,直到其他執行緒調用countDown方法使當前計數器的值變為零,每次調用countDown計數器的值減1。當計數器值減至零時,所有因調用await()方法而處於等待狀態的執行緒就會繼續往下執行。這種現象只會出現一次,因為計數器不能被重置,如果業務上需要一個可以重置計數次數的版本,可以考慮使用CycliBarrier。

在某些業務場景中,程式執行需要等待某個條件完成後才能繼續執行後續的操作;下面舉個栗子,比如秦國需要滅六國才能一統華夏。

程式碼示例:

public class CountDownLatchDemo {      public static void main(String[] args) throws InterruptedException {          CountDownLatch countDownLatch = new CountDownLatch(6);          for (int i = 1; i <= 6; i++) {              new Thread(() -> {                  System.out.println(Thread.currentThread().getName() + "t國,被滅");                  countDownLatch.countDown();              }, Objects.requireNonNull(CountryEnum.forEachCountryEnum(i)).getRetMessage()).start();          }          countDownLatch.await();          System.out.println(Thread.currentThread().getName() + "t秦滅六國,一統華夏");      }  }
public enum CountryEnum {        ONE(1, "齊"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "趙"), FIVE(5, "魏"), SIX(6, "韓");        private Integer retCode;      private String retMessage;        CountryEnum(Integer retCode, String retMessage) {          this.retCode = retCode;          this.retMessage = retMessage;      }        public static CountryEnum forEachCountryEnum(int index) {          CountryEnum[] values = CountryEnum.values();          for (CountryEnum value : values) {              if (index == value.getRetCode()) {                  return value;              }          }          return null;      }        public Integer getRetCode() {          return retCode;      }        public String getRetMessage() {          return retMessage;      }}

CyclicBarrier

CyclicBarrier(可重用屏障/柵欄)類似於CountDownLatch(倒計數閉鎖),它能阻塞一組執行緒直到某個事件的發生。

  • 與閉鎖的關鍵區別在於,所有的執行緒必須同時到達屏障位置,才能繼續執行。
  • 閉鎖用於等待事件,而屏障用於等待其他執行緒。
  • CyclicBarrier 可以使一定數量的執行緒反覆地在屏障位置處彙集。當執行緒到達屏障位置時將調用 await() 方法,這個方法將阻塞直到所有執行緒都到達屏障位置。如果所有執行緒都到達屏障位置,那麼屏障將打開,此時所有的執行緒都將被釋放,而屏障將被重置以便下次使用。

  • 所謂 Cyclic 即循環的意思,所謂 Barrier 即屏障的意思。
  • CyclicBarrier是一個同步輔助類,它允許一組執行緒相互等待直到所有執行緒都到達一個公共的屏障點。
  • 在程式中有固定數量的執行緒,這些執行緒有時候必須等待彼此,這種情況下,使用 CyclicBarrier 很有幫助。
  • 這個屏障之所以用循環修飾,是因為在所有的執行緒釋放彼此之後,這個屏障是可以 重新使用 的。

舉個栗子:

public class CyclicBarrierDemo {      public static void main(String[] args) {          CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("召喚神龍"));          for (int i = 1; i <= 7; i++) {              final int tempInt = i;              new Thread(() -> {                  System.out.println(Thread.currentThread().getName() + "t收集到第: " + tempInt + "龍珠");                  try {                      cyclicBarrier.await();                  } catch (Exception e) {                      e.printStackTrace();                  }              }, i + "").start();          }      }  }

Semaphore

訊號量Semaphore是一個控制訪問多個共享資源的計數器,和CountDownLatch一樣,其本質上是一個「共享鎖」。

Semaphore,在API是這麼介紹的:

一個計數訊號量。從概念上講,訊號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個acquire(),然後再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore只對可用許可的號碼進行計數,並採取相應的行動。

下面我們就一個停車場的簡單例子來闡述Semaphore:

假設停車場僅有5個停車位,一開始停車場沒有車輛所有車位全部空著,然後先後到來三輛車,停車場車位夠,安排進去停車,然後又來三輛,這個時候由於只有兩個停車位,所有隻能停兩輛,其餘一輛必須在外面候著,直到停車場有空車位,當然以後每來一輛都需要在外面候著。當停車場有車開出去,裡面有空位了,則安排一輛車進去(至於是哪輛 要看選擇的機制是公平還是非公平)。

從程式角度看,停車場就相當於訊號量Semaphore,其中許可數為5,車輛就相對執行緒。當來一輛車時,許可數就會減 1 ,當停車場沒有車位了(許可書 == 0 ),其他來的車輛需要在外面等候著。如果有一輛車開出停車場,許可數 + 1,然後放進來一輛車。

號量Semaphore是一個非負整數(>=1)。當一個執行緒想要訪問某個共享資源時,它必須要先獲取Semaphore,當Semaphore >0時,獲取該資源並使Semaphore – 1。如果Semaphore值 = 0,則表示全部的共享資源已經被其他執行緒全部佔用,執行緒必須要等待其他執行緒釋放資源。當執行緒釋放資源時,Semaphore則+1

程式碼演示:

public class SemaphoreDemo {      public static void main(String[] args) {          // 模擬5個停車位,10輛車去搶車位          // Semaphore內部包含公平鎖(FairSync)和非公平鎖(NonfairSync)默認為非公平鎖          Semaphore semaphore = new Semaphore(5);          // 模擬六部汽車          for (int i = 1; i <= 10; i++) {              new Thread(() -> {                  try {                      semaphore.acquire();                      System.out.println(Thread.currentThread().getName() + "t搶到車位");                      Thread.sleep(3000);                      System.out.println(Thread.currentThread().getName() + "t停車3秒後,離開車位");                  } catch (InterruptedException e) {                      e.printStackTrace();                  } finally {                      semaphore.release();                  }              }, i + "").start();          }      }  }

CyclicBarrier、CountDownLatch、Semaphore 的小總結

  • CountDownLatch 是一個執行緒(或者多個),等待另外 N 個執行緒完成某個事情之後才能執行;CyclicBarrier 是 N 個執行緒相互等待,任何一個執行緒完成之前,所有的執行緒都必須等待。
  • CountDownLatch 的計數器只能使用一次。而 CyclicBarrier 的計數器可以使用 reset() 方法重置;CyclicBarrier 能處理更為複雜的業務場景,比如如果計算髮生錯誤,可以重置計數器,並讓執行緒們重新執行一次。
  • CountDownLatch 採用減計數方式;CyclicBarrier 採用加計數方式;Semaphore更像是加減法。
  • Semaphore 主要用於控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,始終保持一定數量內的執行緒去使用公共資源。

Tags: