「每日分享」共享鎖實現原理及CountDownLatch解析

點擊上方"java全棧技術"關注,每天學習一個java知識點,喜歡的也可以關注微信公眾號"ITeye"

前言

本篇主要通過CountDownLatch的學習來了解java併發包中是如何實現共享鎖的。

CountDownLatch使用講解

CountDownLatch是java5中新增的一個併發工具類,其使用非常簡單,下面通過偽代碼簡單看一下使用方式:

「每日分享」共享鎖實現原理及CountDownLatch解析

這是一個使用CountDownLatch非常簡單的例子,創建的時候,需要指定一個初始狀態值,本例為2,主線程調用 latch.await時,除非latch狀態值為0,否則會一直阻塞休眠。當所有任務執行完後,主線程喚醒,最終執行打印動作。

以上只是一個最簡單的例子,接著咱們再來看一個,這回,咱們想要在任務執行完後做更多的事情,如下圖所示:

「每日分享」共享鎖實現原理及CountDownLatch解析

這一次,在線程3和線程4中,分別調用了latch.await(),當latch狀態值為0時,這兩個線程將會繼續執行任務,但是順序性是無法保證的。

CountDownLatch的方便之處在於,你可以在一個線程中使用,也可以在多個線程上使用,一切只依據狀態值,這樣便不會受限於任何的場景。

java共享鎖模型

在java5提供的併發包下,有一個AbstractQueuedSynchronizer抽象類,也叫AQS,此類根據大部分併發共性作了一些抽象,便於開發者實現如排他鎖,共享鎖,條件等待等更高級的業務功能。它通過使用CAS和隊列模型,出色的完成了抽象任務,在此向Doug Lea致敬。

AQS比較抽象,並且是優化精簡的代碼,如果一頭扎進去,可能會比較容易迷失。本篇只解說CountDownLatch中使用到的共享鎖模型。

我們以CountDownLatch第二個例子作為案例來分析一下,一開始,我們創建了一個CountDownLatch實例,

「每日分享」共享鎖實現原理及CountDownLatch解析

此時,AQS中,狀態值state=2,對於 CountDownLatch 來說,state=2表示所有調用await方法的線程都應該阻塞,等到同一個latch被調用兩次countDown後才能喚醒沉睡的線程。接著線程3和線程4執行了 await方法,這會的狀態圖如下:

「每日分享」共享鎖實現原理及CountDownLatch解析

注意,上面的通知狀態是節點的屬性,表示該節點出隊後,必須喚醒其後續的節點線程。當線程1和線程2分別執行完latch.countDown方法後,會把state值置為0,此時,通過CAS成功置為0的那個線程將會同時承擔起喚醒隊列中第一個節點線程的任務,從上圖可以看出,第一個節點即為線程3,當線程3恢復執行之後,其發現狀態值為通知狀態,所以會喚醒後續節點,即線程4節點,然後線程3繼續做自己的事情,到這裡,線程3和線程4都已經被喚醒,CountDownLatch功成身退。

上面的流程,如果落實到代碼,把 state置為0的那個線程,會判斷head指向節點的狀態,如果為通知狀態,則喚醒後續節點,即線程3節點,然後head指向線程3節點,head指向的舊節點會被刪除掉。當線程3恢復執行後,發現自身為通知狀態,又會把head指向線程4節點,然後刪除自身節點,並喚醒線程4。

這裡可能讀者會有個疑問,線程節點的狀態是什麼時候設置上去的。其實,一個線程在阻塞之前,就會把它前面的節點設置為通知狀態,這樣便可以實現鏈式喚醒機制了。

結束語

本篇從CountDownLatch入手講解AQS中的共享鎖模式,主要是由CountDownLatch的實現相對簡單,但卻實現了共享鎖模型,如果在理解了模型的基礎上,從CountDownLatch入手來看AQS關於共享鎖的代碼還比較好看懂,在看的時候,建議以看懂大致內容為主,學習其設計的思路,不要陷入所有條件處理細節中,多線程環境中,對與錯有時候不是那麼容易看出來的。

原文鏈接:https://blog.csdn.net/yanyan19880509/article/details/52349056


分享到:


相關文章: