1. 程式人生 > >java並發的基本概念和級別

java並發的基本概念和級別

結構 樂觀 live 執行 完成後 不成功 CP 被占用 如果

並發的概念:

並發(Concurrency)和並行(Parallelism)

並發偏重於多個任務交替執行,而多個任務之間有可能還是串行的。而並行是真正意義上的“同時執行”。嚴格意義上來說,並行的多個任務是真實的同時執行,而對於並發來說,這個過程只是交替的,一會兒運行任務A一會兒執行任務B,系統會不停地在兩者間切換。但對於外部觀察者來說,即使多個任務之間是串行並發的,也會造成多任務間是並行執行的錯覺。真實的並行也只可能出現在擁有多個CPU的系統中(比如多核CPU)。

臨界區

臨界區用來表示一種公共資源或者說是共享數據,可以被多個線程使用。但是每一次,只能有一個線程使用它,一旦臨界區資源被占用,其他線程要想使用這個資源,就必須等待。

阻塞(Blocking)和非阻塞(Non-Blocking)

阻塞和非阻塞通常用來形容多線程間的相互影響。比如一個線程占用了臨界區資源,那麽其他所有需要這個資源的線程就必須在這個臨界區中進行等待。等待會導致線程掛起,這種情況就是阻塞。非阻塞的意思與之相反,它強調沒有一個線程可以妨礙其他線程執行。所有的線程都會嘗試不斷前向執行。

死鎖(Deadlock)、饑餓(Starvation)和活鎖(Livelock)

死鎖、饑餓和活鎖都屬於多線程的活躍性問題。如果發現上述幾種情況,那麽相關線程可能很難再繼續往下執行了。
死鎖是幾個線程彼此之間相互占用了其它線程的資源,如果大家都不願意釋放自己的資源,那麽這個狀態將永遠維持下去。與死鎖相比,饑餓還是有可能在未來一段時間內解決的(比如高優先級的線程已經完成任務,不再瘋狂的執行)。活鎖是一種有趣的情況,比如當你要坐電梯下樓,電梯到了,門開了,這時你正準備出去。但很不巧的是,門外一個人擋著你的去路,他想進來。然後你們向同一個方向禮讓,結果,你們倆就又撞上了……

並發的級別

由於臨界區的存在,多線程之間的並發必須受到控制。根據控制並發的策略,可以把並發的級別進行分類,大致上可以分為阻塞、無饑餓、無障礙、無鎖、無等待幾種。

阻塞(Blocking)

一個線程是阻塞的,那麽在其他線程釋放資源之前,當前線程無法繼續執行。當我們使用synchronized關鍵字,或者重入鎖時就會產生阻塞的線程。無論是synchronized或者重入鎖,都會試圖在執行後續代碼前,得到臨界區的鎖,如果得不到,線程就會被掛起等待,直到占有了所需資源為止。

無饑餓(Starvation-Free)

這個取決於線程之間是否有優先級的存在,如果系統允許高優先級的線程插隊。這樣有可能導致低優先級線程產生饑餓。

無障礙(Obstruction-Free)

無障礙是一種最弱的非阻塞調度。兩個線程如果是無障礙的執行,那麽他們不會因為臨界區的問題導致一方被掛起。換言之,大家都進入臨界區了。那麽如果一起修改共享數據,把數據改壞了可怎麽辦呢?對於無障礙的線程來說,一旦檢測到這種情況,它就會立即對自己所做的修改進行回滾,確保數據安全。如果說阻塞的控制方式是悲觀策略,相對來說非阻塞的調度就是一種樂觀的策略。
從這個策略中也可以看到,當臨界區中存在嚴重的沖突時,所有的線程可能都會不斷地回滾自己的操作,而沒有一個線程可以走出臨界區。這種情況會影響系統的正常執行。
一種可行的無障礙實現可以依賴一個“一致性標記”來實現。線程在操作之前,先讀取並保存這個標記,在操作完成後,再次讀取,檢查這個標記是否被更改過,如果兩者是一致的,則說明資源訪問沒有沖突。如果不一致,則說明資源可能在操作過程中與其他寫線程沖突,需要重試操作。而任何對資源有修改操作的線程,在修改數據前,都需要更新這個一致性標記,表示數據不再安全。

無鎖(Lock-Free)

無鎖的並行都是無障礙的。在無鎖的情況下,所有的線程都能嘗試對臨界區進行訪問,但不同的是,無鎖的並發保證必然有一個線程能夠在有限步內完成操作離開臨界區,一個典型的特點是可能會包含一個無窮循環。在這個循環中,線程會不斷嘗試修改共享變量。如果修改成功,程序退出,否則繼續嘗試修改。但無論如何,無鎖的並行總能保證有一個線程是可以勝出的。至於臨界區中競爭失敗的線程,它們則不斷重試,直到自己獲勝。如果總是嘗試不成功,則會出現類似饑餓的現象,線程會停止不前。

無等待(Wait-Free)

無鎖只要求有一個線程可以在有限步內完成操作,而無等待則在無鎖的基礎上更進一步進行擴展。它要求所有的線程都必須在有限步內完成,這樣就不會引起饑餓問題。如果限制這個步驟上限,還可以進一步分解為有界無等待和線程數無關的無等待幾種,它們之間的區別只是對循環次數的限制不同。一種典型的無等待結構就是RCU(Read-Copy-Update)。它的基本思想是,對數據的讀可以不加控制。因此,所有的讀線程都是無等待的,它們既不會被鎖定等待也不會引起任何沖突。但在寫數據的時候,先取得原始數據的副本,接著只修改副本數據(這就是為什麽讀可以不加控制),修改完成後,在合適的時機回寫數據。

java並發的基本概念和級別