天天看點

【多線程】Java的各種鎖機制、鎖的優化/更新(無鎖、偏向鎖、自旋鎖、重量級鎖)

1、各種鎖概括

【多線程】Java的各種鎖機制、鎖的優化/更新(無鎖、偏向鎖、自旋鎖、重量級鎖)

2、樂觀鎖與悲觀鎖

其實樂觀鎖和悲觀鎖隻是一種思想,是對于線程同步的不同看法。

悲觀鎖

擷取同步資料的時候會加鎖,以防止被其他線程修改。

對于同一個資料的并發操作,悲觀鎖認為自己在使用資料的時候一定有别的線程來修改資料,是以在擷取資料的時候會先加鎖,確定資料不會被别的線程修改。Java中,synchronized關鍵字和Lock的實作類都是悲觀鎖。

樂觀鎖

不會添加鎖,但是會在更新資料的時候判斷資料是否和之前一樣(Compare and Swap)。

如果這個資料沒有被更新,目前線程将自己修改的資料成功寫入。如果資料已經被其他線程更新,則根據不同的實作方式執行不同的操作(例如報錯或者自動重試)。最常采用的是CAS算法,Java原子類中的遞增操作就是通過CAS自旋實作的。

簡單說一下CAS的缺點:

1、ABA問題;

JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題,具體操作封裝在compareAndSet()中。compareAndSet()首先檢查目前引用和目前标志與預期引用和預期标志是否相等,如果都相等,則以原子方式将引用值和标志的值設定為給定的更新值。

2、不斷自旋循環時間太長開銷大;

3、隻能保證一個共享變量的原子操作。

Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,可以把多個變量放在一個對象裡來進行CAS操作。

對比

  • 悲觀鎖适合寫操作較多的場景,先加鎖可以保證寫操作時資料正确。
  • 樂觀鎖适合讀操作多的場景,不加鎖的特點能夠使其讀操作的性能大幅提升。
【多線程】Java的各種鎖機制、鎖的優化/更新(無鎖、偏向鎖、自旋鎖、重量級鎖)
——————————————————————————————————————————————————————————

3、自旋鎖和适應性自旋鎖

自旋鎖

由于阻塞和喚醒一個Java線程需要作業系統切換CPU狀态來完成,這種狀态轉換需要耗費處理器時間,而在許多場景中,同步資源鎖定的時間比較短,是以可以讓請求鎖的線程稍等一段時間(自旋),假如自旋完成後鎖定同步資源的線程已經釋放了鎖,那麼目前線程就可以不必阻塞而是直接擷取同步資源。

但是雖然自旋避免了線程切換的開銷,但是要占用處理器時間,自旋時間太久會浪費處理器資源,是以自旋等待有限制次數,預設是10次(可以使用-XX:PreBlockSpin來更改),假如沒有成功獲得鎖,那麼就應該挂起線程。

阻塞或喚醒一個Java線程需要作業系統切換CPU狀态來完成,這種狀态轉換需要耗費處理器時間。如果同步代碼塊中的内容過于簡單,狀态轉換消耗的時間有可能比使用者代碼執行的時間還要長。

在許多場景中,同步資源的鎖定時間很短,為了這一小段時間去切換線程,線程挂起和恢複現場的花費可能會讓系統得不償失。如果實體機器有多個處理器,能夠讓兩個或以上的線程同時并行執行,我們就可以讓後面那個請求鎖的線程不放棄CPU的執行時間,看看持有鎖的線程是否很快就會釋放鎖。

而為了讓目前線程“稍等一下”,我們需讓目前線程進行自旋,如果在自旋完成後前面鎖定同步資源的線程已經釋放了鎖,那麼目前線程就可以不必阻塞而是直接擷取同步資源,進而避免切換線程的開銷。這就是自旋鎖。

【多線程】Java的各種鎖機制、鎖的優化/更新(無鎖、偏向鎖、自旋鎖、重量級鎖)

适應性自旋鎖

自旋鎖在JDK1.4.2中引入,使用-XX:+UseSpinning來開啟。JDK 6中變為預設開啟,并且引入了自适應的自旋鎖(适應性自旋鎖)。

自适應意味着自旋的時間(次數)不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀态來決定。

如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運作中,那麼虛拟機就會認為這次自旋也是很有可能再次成功,進而它将允許自旋等待持續相對更長的時間。如果對于某個鎖,自旋很少成功獲得過,那在以後嘗試擷取這個鎖時将可能省略掉自旋過程,直接阻塞線程,避免浪費處理器資源。

在自旋鎖中 另有三種常見的鎖形式:TicketLock、CLHlock和MCSlock。

4、鎖的更新

這四種鎖指的是鎖的狀态,專門針對synchronized的。

Synchronized實作線程同步的原理

synchronized是悲觀鎖,在操作同步資源之前需要給同步資源先加鎖,這把鎖就是存在Java對象頭裡的

Java對象頭: Mark Word(标記字段)、Klass Pointer(類型指針)

MarkWord:存儲了預設存儲對象的HashCode,分代年齡和鎖标志位資訊。在運作期間MarkWord裡存儲的資料會随着鎖标志位的變化而變化。

Klass Pointer:對象指向它的類中繼資料的指針,虛拟機通過這個指針來确定這個對象是哪個類的執行個體。

Monitor: 它是依賴于底層作業系統的Mutex Lock互斥鎖(Monitor enter、Monitor exit)來實作的線程同步。

synchronized最初實作同步就是依賴的Mutex Lock,效率低,是重量級鎖。

Monitor是線程私有的資料結構,每一個線程都有一個可用monitor record清單,同時還有一個全局的可用清單。每一個被鎖住的對象都會和一個monitor關聯,同時monitor中有一個Owner字段存放擁有該鎖的線程的唯一辨別,表示該鎖被這個線程占用。

為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了 偏向鎖 和 輕量級鎖。

優化Synchronzied(更新):

更新過程:無鎖—>偏向鎖—>輕量級鎖—>重量級鎖

【多線程】Java的各種鎖機制、鎖的優化/更新(無鎖、偏向鎖、自旋鎖、重量級鎖)
  • 無鎖 無鎖狀态,不需要加鎖解鎖(标志位01);

沒有對資源進行鎖定,所有的線程都能通路并修改同一個資源,但同時隻有一個線程能修改成功。

while{有沖突就不修改,不沖突就修改}

  • 偏向鎖 然後當有一個線程開始通路同步塊時,該線程會自動擷取鎖(降低擷取鎖的代價),更新為偏向鎖。(更新主要就是改變對象頭的MARKWORD)

優點: 加鎖解鎖不需要額外的消耗,适用于隻有一個線程通路同步塊場景;

缺點: 如果線程間存在鎖競争,會帶來額外鎖撤銷的開銷。(隻要有線程競争資源,就撤銷鎖)

第一次線程通路同步塊并擷取鎖時,會在MarkWord裡存儲鎖偏向的線程ID,通過CAS修改對象頭内容,讓鎖對象變成偏向鎖;

第二次到達該同步塊,會檢測Mark Word裡是否存儲着指向目前線程的偏向鎖,判斷此時持有鎖的線程是否是自己,是的話不需要加鎖,正常往下執行。(是以,當隻有一個線程操作的時候,幾乎不需要啥加鎖解鎖的開銷)

偏向鎖隻有遇到其他線程嘗試競争偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有位元組碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處于被鎖定狀态。撤銷偏向鎖後恢複到無鎖(标志位為“01”)或輕量級鎖(标志位為“00”)的狀态。

偏向鎖在JDK 6及以後的JVM裡是預設啟用的。可以通過JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,關閉之後程式預設會進入輕量級鎖狀态。

  • 自旋鎖 然後遇到其他線程嘗試競争偏向鎖時,更新為自旋鎖(輕量級鎖,标志位00)

優點: 競争的線程不會阻塞,提高了程式的響應速度,适用于追求響應速度,同步塊執行速度快的場景;

缺點: 如果始終得不到競争的資源,使用自旋會消耗CPU

在輕量級鎖狀态下繼續鎖競争,沒有搶到鎖的線程将自旋,即不停地循環判斷鎖是否能夠被成功擷取。

能等就不要出動作業系統,畢竟開銷太大了

在代碼進入同步塊的時候,如果同步對象鎖狀态為無鎖狀态(鎖标志位為“01”狀态,是否為偏向鎖為“0”),虛拟機首先将在目前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的Mark Word的拷貝,然後拷貝對象頭中的Mark Word複制到鎖記錄中。

拷貝成功後,虛拟機将使用CAS操作嘗試将對象的Mark Word更新為指向Lock Record的指針,并将Lock Record裡的owner指針指向對象的Mark Word。

如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖标志位設定為“00”,表示此對象處于輕量級鎖定狀态。

如果輕量級鎖的更新操作失敗了,虛拟機首先會檢查對象的Mark Word是否指向目前線程的棧幀,如果是就說明目前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行,否則說明多個線程競争鎖。

若目前隻有一個等待線程,則該線程通過自旋進行等待。但是當自旋超過一定的次數,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖更新為重量級鎖。

  • 重量級鎖 自旋十次失敗後,更新為重量級鎖(标志位11)

優點: 不使用自旋,不會消耗CPU,适用于追求吞吐量,同步塊執行速度較慢的場景。

缺點: 線程阻塞,響應時間緩慢

鎖标志的狀态值變為“10”,此時Mark Word中存儲的是指向重量級鎖的指針,此時等待鎖的線程都會進入阻塞狀态。

之是以重量級,是因為它的實作依賴于低層作業系統的Mutex Lock(互斥鎖)實作的,而作業系統實作線程的切換需要從使用者态轉換為核心态,成本非常高。

5、公平鎖與非公平鎖

參考部落格:

深度分析:鎖更新過程和鎖狀态,看完這篇你就懂了!

https://www.cnblogs.com/zhengzhiwei/p/13139326.html

繼續閱讀