java高併發系列 - 第2天:併發級別

因爲臨界區的存在,多線程之間的併發必須受到控制。根據控制併發的策略,咱們能夠把併發的級別分爲阻塞無飢餓無障礙無鎖無等待幾種。java

阻塞

一個線程是阻塞的,那麼在其餘線程釋放資源以前,當前線程沒法繼續執行。當咱們使用synchronized關鍵字或者重入鎖時,咱們獲得的就是阻塞的線程。數據庫

synchronize關鍵字和重入鎖都試圖在執行後續代碼前,獲得臨界區的鎖,若是得不到,線程就會被掛起等待,直到佔有了所需資源爲止。安全

無飢餓(Starvation-Free)

若是線程之間是有優先級的,那麼線程調度的時候老是會傾向於先知足高優先級的線程。也就是說,對於同一個資源的分配,是不公平的!圖1.7中顯示了非公平鎖與公平鎖兩種狀況(五角星表示高優先級線程)。對於非公平鎖來講,系統容許高優先級的線程插隊。這樣有可能致使低優先級線程產生飢餓。但若是鎖是公平的,按照先來後到的規則,那麼飢餓就不會產生,無論新來的線程優先級多高,要想得到資源,就必須乖乖排隊,這樣全部的線程都有機會執行。多線程

無障礙(Obstruction-Free)

無障礙是一種最弱的非阻塞調度。兩個線程若是無障礙地執行,那麼不會由於臨界區的問題致使一方被掛起。換言之,你們均可以大搖大擺地進入臨界區了。那麼你們一塊兒修改共享數據,把數據改壞了怎麼辦呢?對於無障礙的線程來講,一旦檢測到這種狀況,它就會當即對本身所作的修改進行回滾,確保數據安全。但若是沒有數據競爭發生,那麼線程就能夠順利完成本身的工做,走出臨界區。併發

若是說阻塞的控制方式是悲觀策略,也就是說,系統認爲兩個線程之間頗有可能發生不幸的衝突,所以以保護共享數據爲第一優先級,相對來講,非阻塞的調度就是一種樂觀的策略。它認爲多個線程之間頗有可能不會發生衝突,或者說這種機率不大。所以你們都應該無障礙地執行,可是一旦檢測到衝突,就應該進行回滾。高併發

從這個策略中也能夠看到,無障礙的多線程程序並不必定能順暢運行。由於當臨界區中存在嚴重的衝突時,全部的線程可能都會不斷地回滾本身的操做,而沒有一個線程能夠走出臨界區。這種狀況會影響系統的正常執行。因此,咱們可能會很是但願在這一堆線程中,至少能夠有一個線程可以在有限的時間內完成本身的操做,而退出臨界區。至少這樣能夠保證系統不會在臨界區中進行無限的等待。atom

一種可行的無障礙實現能夠依賴一個"一致性標記"來實現。線程在操做以前,先讀取並保存這個標記,在操做完成後,再次讀取,檢查這個標記是否被更改過,若是二者是一致的,則說明資源訪問沒有衝突。若是不一致,則說明資源可能在操做過程當中與其餘線程衝突,須要重試操做。而任何對資源有修改操做的線程,在修改數據前,都須要更新這個一致性標記,表示數據再也不安全。線程

數據庫中樂觀鎖,應該比較熟悉,表中須要一個字段version(版本號),每次更新數據version+1,更新的時候將版本號做爲條件進行更新,根據更新影響的行數判斷更新是否成功,僞代碼以下:code

1.查詢數據,此時版本號爲w_v
2.打開事務
3.作一些業務操做
4.update t set version = version+1 where id = 記錄id and version = w_v;//此行會返回影響的行數c
5.if(c>0){
        //提交事務
    }else{
        //回滾事務
    }

多個線程更新同一條數據的時候,數據庫會對當前數據加鎖,同一時刻只有一個線程能夠執行更新語句。blog

無鎖(Lock-Free)

無鎖的並行都是無障礙的。在無鎖的狀況下,全部的線程都能嘗試對臨界區進行訪問,但不一樣的是,無鎖的併發保證必然有一個線程可以在有限步內完成操做離開臨界區。

在無鎖的調用中,一個典型的特色是可能會包含一個無窮循環。在這個循環中,線程會不斷嘗試修改共享變量。若是沒有衝突,修改爲功,那麼程序退出,不然繼續嘗試修改。但不管如何,無鎖的並行總能保證有一個線程是能夠勝出的,不至於全軍覆沒。至於臨界區中競爭失敗的線程,他們必須不斷重試,直到本身獲勝。若是運氣很很差,老是嘗試不成功,則會出現相似飢餓的先寫,線程會中止。

下面就是一段無鎖的示意代碼,若是修改不成功,那麼循環永遠不會中止。

while(!atomicVar.compareAndSet(localVar, localVar+1)){
        localVal = atomicVar.get();
}

無等待

無鎖只要求有一個線程能夠在有限步內完成操做,而無等待則在無鎖的基礎上更進一步擴展。它要求全部線程都必須在有限步內完成,這樣不會引發飢餓問題。若是限制這個步驟的上限,還能夠進一步分解爲有界無等待和線程數無關的無等待等幾種,他們之間的區別只是對循環次數的限制不一樣。

一種典型的無等待結果就是RCU(Read Copy Update)。它的基本思想是,對數據的讀能夠不加控制。所以,全部的讀線程都是無等待的,它們既不會被鎖定等待也不會引發任何衝突。但在寫數據的時候,先獲取原始數據的副本,接着只修改副本數據(這就是爲何讀能夠不加控制),修改完成後,在合適的時機回寫數據。

java高併發系列交流羣

相關文章
相關標籤/搜索