從火箭發場景來學習Java多線程並發閉鎖對象

  • 2020 年 3 月 18 日
  • 筆記

從火箭發場景來學習Java多線程並發閉鎖對象

倒計時器場景

在我們開發過程中,有時候會使用到倒計時計數器。最簡單的是:int size = 5; 執行後,size—這種方式來實現。但是在多線程並發的情況下,這種操作會不安全的。舉個現實中最典型的一個例子:火箭發射的案例。

大家都看過火箭發射的直播吧。火箭在發送的時候,有很多設備需要檢查是否都準備就緒。在總控室得到所有設備都準備就緒後,才會下達發射的命令。我們也知道,火箭發射有很多設備需要檢驗,這不是一個部門一個一個檢查的,而是多個部門協同配合實現的。如果把一個個部門看作不同的線程的話。我們就可以假設:

如果是一個部門一個一個設備檢查,這就是單線程操作的;

如果是多個部門協同配合的話,就是多線程的。

所以說,在火箭發射前檢查設備是 多線程情況下進行的。

我們也不知道,不同部門負責檢查的設備的複雜度不同,速度不同,就會導致有些部門檢查完成的快,有些部門檢查完成的慢。這個過程我們可以理解為不同線程在競爭CPU資源的時候不同。

假設有5個部門同時協同工作,這5個部門的操作可以看作是一組操作。因為速度不同,那麼總控室下達發射的命令是以哪個檢查完畢為準呢?是A部門還是B部門或者D部門呢?都不是,總控室下達發射的命令是在得到5個部門都檢查完畢後才會下達發射的命令。我們也可以理解為總控室在得到這一組(五個部門)都操作完成才執行的。此時總控室可以理解為一個線程,五個部門一組可以理解一個線程組。

從上面現實生活中的案例分析,我們來想想上面的操作用Java程序怎麼實現 ?

使用count—的代碼實現

模擬不同部門的線程:

我們先來看看常規的。使用count–的效果。

模擬總控室的主線程:

從上面代碼中,我們可以看到當計數器count減到0的時候,總控室下達發射命令。這個從邏輯上來說,是正確的,沒問題的。我們來看看運行結果:

運行結果:

從運行結果,我們可以看到,當總控室接收到count =0的指令後,認為各個部門都已經檢查完畢了。所以就下達了發射命令。但是在最後,我們發現火箭已經發射了,部門4和5才向總控室彙報檢查完畢,準備就緒。這種情況是很危險的。因為我們知道火箭的造價是很昂貴的而且凝聚了很多科研人員的心血。如果因為某個部門未檢查完畢就發射而導致火箭發射失敗或者墜落,在現實生活中,這種情況是不允許出現的。

那麼這種情況,在Java中,怎麼解決呢?可以使用countdownlatch這個對象來解決。

Countdownlatch

Countdownlatch是什麼?

先來看看源碼中怎麼解釋的:A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

什麼意思呢?

簡單的來說,是一個同步輔助工具類,運行一個或多個線程在等待其他線程完成一組操作後再接着執行的工具類。

實現的流程:

通過一個計數器來實現的,計數器的初始值就是要執行的線程的數量。每當一個線程執行完畢之後,計數器的值就會減一,當計數器的值減少到0的時候,表示所有的線程都執行完畢了。然後再閉鎖上等待的其他線程就可以恢復正常工作了。

來看看主要的方法

說明:

Sync:是countdownlatch內部類,繼承AbstractQueuedSynchronizer使用AQS狀態來代替計數的。

有參構造器:public CountDownLatch(int count){}

await():等待方法。還有一個帶參數重載的方法。

countdown():執行計數器減1操作的方法。

使用countdownlatch模擬火箭發射前準備代碼:

我們來看看使用countdownlatch來模擬火箭發射前準備會不會出問題。

來看看模擬部門的線程代碼:

為了保證計數器減一操作不受子線程運行結果影響,講count.coundDown()操作放在finally代碼塊裏面。

再來看看總控室下達發射命令的主線程:

在downLatch.await()之後,下達發射命令。

查看運行結果:

 

我們可以看到,當所有部門都準備就緒後,總控室接收到完成的指令後,下達發射火箭命令。這個流程才是正常的。從運行結果來看也是正常的。

使用場景:

場景1:某線程在運行前需要等待其他N個線程執行完成之後在執行。

比如:

容器啟動spring 容器的啟動,需要初始化、bean裝配、檢查其他依賴等加載完畢之後,在主線程在繼續執行;

在比如:電商中統計庫存問題。我們知道,一個電商項目有很多分類,不同分類下的庫存不一樣。如果要統計當前剩餘總庫存。這個時候,就可以使用其他線程統計每個分類下的庫存。等所有分類都統計完成之後,主線程在進行匯總操作。

 

關注凱哥:

個人博客:www.kaigejava.com

個人公眾號:凱哥Java(kaigejava)