從火箭發(fā)場景來學(xué)習(xí)Java多線程并發(fā)閉鎖對象
倒計時器場景
在我們開發(fā)過程中咬清,有時候會使用到倒計時計數(shù)器。最簡單的是:int size = 5; 執(zhí)行后关带,size—這種方式來實現(xiàn)治专。但是在多線程并發(fā)的情況下,這種操作會不安全的裂问。舉個現(xiàn)實中最典型的一個例子:火箭發(fā)射的案例侧啼。
大家都看過火箭發(fā)射的直播吧牛柒。火箭在發(fā)送的時候痊乾,有很多設(shè)備需要檢查是否都準(zhǔn)備就緒皮壁。在總控室得到所有設(shè)備都準(zhǔn)備就緒后,才會下達發(fā)射的命令哪审。我們也知道闪彼,火箭發(fā)射有很多設(shè)備需要檢驗,這不是一個部門一個一個檢查的协饲,而是多個部門協(xié)同配合實現(xiàn)的畏腕。如果把一個個部門看作不同的線程的話。我們就可以假設(shè):
如果是一個部門一個一個設(shè)備檢查茉稠,這就是單線程操作的描馅;
如果是多個部門協(xié)同配合的話,就是多線程的而线。
所以說铭污,在火箭發(fā)射前檢查設(shè)備是 多線程情況下進行的。
我們也不知道膀篮,不同部門負責(zé)檢查的設(shè)備的復(fù)雜度不同嘹狞,速度不同,就會導(dǎo)致有些部門檢查完成的快誓竿,有些部門檢查完成的慢磅网。這個過程我們可以理解為不同線程在競爭CPU資源的時候不同。
假設(shè)有5個部門同時協(xié)同工作筷屡,這5個部門的操作可以看作是一組操作涧偷。因為速度不同,那么總控室下達發(fā)射的命令是以哪個檢查完畢為準(zhǔn)呢毙死?是A部門還是B部門或者D部門呢?都不是燎潮,總控室下達發(fā)射的命令是在得到5個部門都檢查完畢后才會下達發(fā)射的命令。我們也可以理解為總控室在得到這一組(五個部門)都操作完成才執(zhí)行的扼倘。此時總控室可以理解為一個線程确封,五個部門一組可以理解一個線程組。
從上面現(xiàn)實生活中的案例分析再菊,我們來想想上面的操作用Java程序怎么實現(xiàn) 爪喘?
使用count—的代碼實現(xiàn)
模擬不同部門的線程:
編輯
?
我們先來看看常規(guī)的。使用count--的效果袄简。
模擬總控室的主線程:
編輯
?
從上面代碼中腥放,我們可以看到當(dāng)計數(shù)器count減到0的時候,總控室下達發(fā)射命令绿语。這個從邏輯上來說秃症,是正確的候址,沒問題的。我們來看看運行結(jié)果:
運行結(jié)果:
編輯
?
從運行結(jié)果种柑,我們可以看到岗仑,當(dāng)總控室接收到count =0的指令后,認為各個部門都已經(jīng)檢查完畢了聚请。所以就下達了發(fā)射命令荠雕。但是在最后,我們發(fā)現(xiàn)火箭已經(jīng)發(fā)射了驶赏,部門4和5才想總控室匯報檢查完畢炸卑,準(zhǔn)備就緒。這種情況是很危險的煤傍。因為我們知道火箭的造價是很昂貴的而且凝聚了很多科研人員的心血盖文。如果因為某個部門未檢查完畢就發(fā)射而導(dǎo)致火箭發(fā)射失敗或者墜落,在現(xiàn)實生活中蚯姆,這種情況是不允許出現(xiàn)的五续。
那么這種情況,在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.
什么意思呢它碎?
簡單的來說,是一個同步輔助工具類铣卡,運行一個或多個線程在等待其他線程完成一組操作后再接著執(zhí)行的工具類链韭。
實現(xiàn)的流程:
通過一個計數(shù)器來實現(xiàn)的偏竟,計數(shù)器的初始值就是要執(zhí)行的線程的數(shù)量煮落。每當(dāng)一個線程執(zhí)行完畢之后,計數(shù)器的值就會減一踊谋,當(dāng)計數(shù)器的的減少到0的時候蝉仇,表示所有的線程都執(zhí)行完畢了。然后再閉鎖上等待的其他線程就可以恢復(fù)正常工作了殖蚕。
來看看主要的方法
編輯
?
說明:
Sync:是countdownlatch內(nèi)部類轿衔,繼承AbstractQueuedSynchronizer使用AQS狀態(tài)來代替計數(shù)的。
有參構(gòu)造器:public CountDownLatch(int count){}
await():等待方法睦疫。還有一個帶參數(shù)重載的方法害驹。
countdown():執(zhí)行計數(shù)器減1操作的方法。
使用countdownlatch模擬火箭發(fā)射前準(zhǔn)備代碼:
我們來看看使用countdownlatch來模擬火箭發(fā)射前準(zhǔn)備會不會出問題蛤育。
來看看模擬部門的線程代碼:
編輯
?
為了保證計數(shù)器減一操作不受子線程運行結(jié)果影響宛官,講count.coundDown()操作放在finally代碼塊里面葫松。
再來看看總控室下達發(fā)射命令的主線程:
編輯
?
在downLatch.await()之后,下達發(fā)射命令底洗。
查看運行結(jié)果:
編輯
?
我們可以看到腋么,當(dāng)所有部門都準(zhǔn)備就緒后,總控室接收到完成的指令后亥揖,下達發(fā)射火箭命令珊擂。這個流程才是正常的。從運行結(jié)果來看也是正常的费变。
使用場景:
場景1:某線程在運行前需要等待其他N個線程執(zhí)行完成之后在執(zhí)行摧扇。
比如:
容器啟動spring 容器的啟動,需要初始化挚歧、bean裝配扳剿、檢查其他依賴等加載完畢之后,在主線程在繼續(xù)執(zhí)行;
在比如:電商中統(tǒng)計庫存問題昼激。我們知道庇绽,一個電商項目有很多分類,不同分類下的庫存不一樣橙困。如果要統(tǒng)計當(dāng)前剩余總庫存瞧掺。這個時候,就可以使用其他線程統(tǒng)計每個分類下的庫存凡傅。等所有分類都統(tǒng)計完成之后辟狈,主線程在進行匯總操作。