1. 程式人生 > >多執行緒學習(7)鎖

多執行緒學習(7)鎖

java主流鎖體系和分類:

1、樂觀鎖,悲觀鎖

悲觀鎖:

悲觀認為自己在使用資料的時候一定有別的執行緒來修改資料,在獲取資料的時候會加鎖,確保資料不會被別的執行緒修改。

鎖實現:關鍵字Synchronized,介面Lock的實現類

適用場景:寫操作較多,先加鎖可以保證寫操作時資料正確

樂觀鎖:

樂觀鎖認為自己在使用資料時,不會有別的執行緒來修改資料,所以不會新增鎖,只是在更新資料的時候去判斷之前的有沒有別的執行緒更新了這個資料。

鎖實現:CAS演算法,例如AtomicInteger類的原子自增就是通過CAS實現的

適用場景:讀操作較多,不加鎖的特點,是它的讀操作效能大大提升

悲觀鎖執行過程:

問題: 阻塞和不阻塞,區別是什麼?

阻塞狀態喚醒,涉及到執行緒的上下文的切換,而不阻塞,就不涉及到上下文的切換。

樂觀鎖的執行過程:

問題:更新失敗了一般怎麼辦?

引用自旋鎖或丟擲異常中斷服務。

2.讀鎖(共享鎖),寫鎖(排它鎖,互斥鎖)

3.自旋鎖,非自旋鎖

自旋鎖和非自旋鎖的執行:

自旋和非自旋鎖的區別:就是是否放棄當前CPU時間片段。

自旋鎖:

指當一個執行緒在獲取鎖的時候,如果鎖已經被其他執行緒獲取,那麼該執行緒將(固定次數的)迴圈等待,然後不斷的判斷鎖是否獲取成功,自旋直到獲取到鎖才會停止。

自旋鎖存在的意義和適用場景:

避免阻塞與喚醒執行緒,因為需要作業系統的CPU狀態,需要消耗一定時間。

同步程式碼塊邏輯簡單,執行時間短。

自旋鎖和自適應自旋鎖

自適應自旋鎖,假定不同執行緒持有同一個物件的時間基本相當,競爭程度趨於穩定,因此,可以根據上一次自旋的時間與結果調整下一次自旋的時間。

比如上一次更新沒有成功,自旋了35次之後成功了。

那麼下一次更新沒有成功,雖然也自旋了35次但還是沒有成功,虛擬機器會增加10次自旋,認為45次自旋也許會成功。

如果上一次自旋了10次就成功了,那麼這次更新也會認為更新10次左右也會成功,而不需要35次。這就是自適應自旋鎖。

達到自選次數了,還不成功,會認為更新失敗,丟擲異常(我們自己控制)。

自適應自旋鎖也有自旋的上限次數,如果每次更新成功,自旋的次數越來越多,會對他降級,減少自選次數,超過降級後的次數還不成功,要麼阻塞執行緒,要麼返回失敗。

JDK1.6可以通過-XX-UseSpinning引數關閉自旋鎖優化,-XX-PreBlockSpin引數修改預設自旋次數

JDK1.7及之後版本,自旋鎖的引數被取消,虛擬機器不再支援由使用者自己配置自旋鎖,自旋鎖總會執行,自旋鎖次數也由JVM自動調整。

4.無鎖,偏向鎖,輕量級鎖,重量級鎖

5.分散式鎖

6.區間鎖(分段鎖)java.util.concurrent.concurrentHashMap

7.可重入鎖,非重入鎖

8.公平鎖,非公平鎖

 

 

樂觀鎖CAS演算法原理:

CAS演算法:全名:比較與交換

無鎖演算法:基於硬體原語實現,在不使用鎖(沒有現成被阻塞)的情況下實現多執行緒之間的變數同步。

JDK中實現:java.util.concurrent包中的院子類AtomicInteger就是通過CAS演算法實現了樂觀鎖

CAS實現原理

演算法涉及到三個運算元:

主記憶體中的V,棧記憶體中進行比較的A,希望給主記憶體重新賦值的B

執行緒1,需要讀寫主記憶體中的一個變數值V,假設V的值現在是0,然後線上程的棧記憶體中,有一個V值的副本,叫做A,A的值目前也是0,經過了一系列操作,計算出了一個新的值B,B為1,現在希望將B的值,寫回主記憶體中,給V重新賦值。

此時會將主記憶體中的V與棧記憶體中的A進行比較,發現值沒有改變,則認為執行緒1可以給主記憶體中的V重新賦值。

如果執行緒2此時棧記憶體中的A值,與主記憶體中的V值不一樣,因為主記憶體中的V值被執行緒1改過了,那麼這時候,執行緒2在做更新操作的時候,肯定不會成功,此時一般會讓執行緒2要麼丟擲異常中斷操作,要麼去進行自旋,當然要設定自旋次數,超過次數就結束,未超過就繼續自旋,直到更新成功。

自旋:實際上就是一種迴圈,去重新load主記憶體區V的值到執行緒的棧當中去,進行第二次的比較與交換,直到成功。

自旋的缺點:該執行緒CAS演算法一直無法執行成功,就會一直佔用CPU資源,對其他執行緒造成飢餓執行緒。

CAS存在哪些問題,以及如何解決

ABA問題:當有三個執行緒,都備份了主記憶體區中的V值,在各自的棧記憶體中修改,假設主記憶體中的V值本來是0,然後執行緒1將V值修改為1,然後執行緒2將主執行緒中的V值又從1改回0,此時執行緒3還沒執行完畢,執行緒3的棧記憶體中V值本身也是0,現在要提交的時候,發現主記憶體的值經過了執行緒1和執行緒2的重新賦值後,還是0。執行緒3感知不到主記憶體的V值有沒有發生過改變,順利更改了V的值為自己的棧記憶體的結果。這就是ABA的問題。

ABA問題的另一個表現,銀行職員挪用公款,在主管未發現的請款下又挪回去了,主管感知不到這筆錢的狀態變化,很危險。

ABA問題如何解決?可以通過增加版本號,來解決這個問題,雖然主記憶體中的值比較的時候是一致的,但是版本號變化了,還是可以讓執行緒去自旋的。這裡所說的版本號是一個抽象的概念,是針對主記憶體區中的物件的,基於硬體原語實現的。

AtomicStampedReference在變數前面新增版本號,每次變數更新的時候,都把版本號加一。

迴圈時間長,開銷大 通過設定自選次數來解決,系統中會有一個預設的自旋次數

只能保證一個共享變數的原子操作(針對一個變數進行操作)

AtomicReference類來保證引用物件之間的原子性,可以把多個變數放在一個物件裡來進行CAS操作。

執行緒獲取同步資源失敗情況下什麼時候自旋、什麼時候阻塞:

 

synchronized原理分析:

 

上下文切換:

當CPU的一個時間片段正在處理執行緒1內的邏輯時,可能執行緒1內的邏輯還沒處理完或者被打斷,就要釋放當前時間片段,跳進執行緒2去處理執行緒2的邏輯,而執行緒1和執行緒2可能讀不在一個程序內,這就叫做執行緒的上下文的切換。

程序與執行緒山下文切換,為什麼消耗資源?