Java中的自旋鎖與適應性自旋鎖詳解
一、為什么需要自旋鎖與適應性自旋鎖
1.1、自旋鎖的提出背景
由于在多處理器環(huán)境中某些資源的有限性,有時需要互斥訪問,這時候就需要引入鎖的概念,只有獲取了鎖的線程才能對資源進行訪問,由于多線程的核心是CPU的時間分片,所以同一時刻只能有一個線程獲取到鎖。那么就面臨一個問題,那么沒有獲取到鎖的線程應該怎么辦?
通常有兩種處理方式:
- 一種是沒有獲取到鎖的線程就一直循環(huán)等待判斷該資源是否已經釋放鎖,這種鎖叫做自旋鎖,它不用將線程阻塞起來(NON-BLOCKING);
- 還有一種處理方式就是把自己阻塞起來,等待重新調度請求,這種叫做互斥鎖。
使用自旋鎖的最大的一個好處是因為自旋鎖存在于linux內核中,如果持有鎖的線程能夠在短時間內釋放鎖,那么那些等待競爭鎖的線程就不需要做內核態(tài)與用戶態(tài)之間的切換從而進入阻塞狀態(tài)。
二、自旋鎖與適應性自旋鎖底層原理解析
2.1、自旋鎖與適應性自旋鎖底層原理解析
自旋鎖早在JDK1.4 中就引入了,只是當時默認時關閉的。在JDK 1.6后默認為開啟狀態(tài)。自旋鎖本質上與阻塞并不相同,先不考慮其對多處理器的要求,如果鎖占用的時間非常的短,那么自旋鎖的性能會非常的好,相反,其會帶來更多的性能開銷(因為在線程自旋時,始終會占用CPU的時間片,如果鎖占用的時間太長,那么自旋的線程會白白消耗掉CPU資源)。
因此自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲取到鎖,就應該使用傳統(tǒng)的方式去掛起線程了,在JDK定義中,自旋鎖默認的自旋次數為10次,用戶可以使用參數-XX:PreBlockSpin來更改。
可是現在又出現了一個問題:如果線程鎖在線程自旋剛結束就釋放掉了鎖,那么是不是有點得不償失。所以這時候我們需要更加聰明的鎖來實現更加靈活的自旋。來提高并發(fā)的性能。(這里則需要自適應自旋鎖!) 在JDK 1.6中引入了自適應自旋鎖。
這就意味著自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋 時間及鎖的擁有者的狀態(tài)來決定的。如果在同一個鎖對象上,自旋等待剛剛成功獲取過鎖,并且持有鎖的線程正在運行中,那么JVM會認為該鎖自旋獲取到鎖的可能性很大,會自動增加等待時間。比如增加到100此循環(huán)。相反,如果對于某個鎖,自旋很少成功獲取鎖。
那再以后要獲取這個鎖時將可能省略掉自旋過程,以避免浪費處理器資源。有了自適應自旋,JVM對程序的鎖的狀態(tài)預測會越來越準確,JVM也會越來越聰明。
三、自旋鎖與適應性自旋鎖的使用
3.1、自旋鎖的實現
public class SpinLockTest { private AtomicBoolean available =new AtomicBoolean(false); public void lock(){ //循環(huán)檢測嘗試獲取鎖 while(!tryLock()){ //doSomething... } } public boolean tryLock(){ //嘗試獲取鎖,成功返回true,失敗返回false return available.compareAndSet(false,true); } public void unLock(){ if(!available.compareAndSet(false,true)){ throw new RuntimeException("釋放鎖失敗"); } } }
3.2、適應性自旋鎖的實現
自旋等待雖然避免了線程切換的開銷,但它要占用處理器時間。如果鎖被占用的時間很短,自旋等待的效果就會非常好。反之,如果鎖被占用的時間很長,那么自旋的線程只會白浪費處理器資源。所以,自旋等待的時間必須要有一定的限度,如果自旋超過了限定次數(默認是10次,可以使用-XX:PreBlockSpin來更改)沒有成功獲得鎖,就應當掛起線程。
自旋鎖的實現原理同樣也是CAS,AtomicInteger中調用unsafe進行自增操作的源碼中的do-while循環(huán)就是一個自旋操作,如果修改數值失敗則通過循環(huán)來執(zhí)行自旋,直至修改成功。 改進的自旋鎖主要有TicketLock、CLHLock、MCSLock,這幾種自旋鎖改進方案內容比較多,實現方式參考 幾種自旋鎖的java實現824b2e4f1eed
四、自旋鎖與適應性自旋鎖中存在的問題與優(yōu)化方案
自旋鎖盡可能的減少線程的阻塞,這對于鎖的競爭不激烈,且占用鎖時間非常短的代碼塊來說性能可以大幅度提升,因為自旋的消耗會小于線程阻塞掛起再喚醒的操作的消耗,這些操作會導致線程發(fā)生兩次上下文切換!
但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間占用鎖執(zhí)行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是占用cpu做無用功,同時有大量線程在競爭一個鎖,會導致獲取鎖的時間很長,線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要cup的線程又不能獲取到cpu,造成 cpu的浪費。所以這種情況下我們要關閉自旋鎖;自旋鎖時間閾值(1.6 引入了適應性自旋鎖)。
自旋鎖的目的是為了占著CPU的資源不釋放,等到獲取到鎖立即進行處理。但是如何去選擇自旋的執(zhí)行時間呢?如果自旋執(zhí)行時間太長,會有大量的線程處于自旋狀態(tài)占用CPU資源,進而會影響整體系統(tǒng)的性能。因此自旋的周期選的額外重要!
JVM 對于自旋周期的選擇,Jdk1.5這個限度是一定的寫死的,在1.6引入了適應性自旋鎖,適應性自旋鎖意味著自旋的時間不在是固定的了,而是由前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態(tài)來決定,基本認為一個線程上下文切換的時間是最佳的一個時間,同時JVM還針對當前CPU的負荷情況做了較多的優(yōu)化,如果平均負載小于CPUs則一直自旋,如果有超過(CPUs/2)個線程正在自旋,則后來線程直接阻塞,如果正在自旋的線程發(fā)現Owner發(fā)生了變化則延遲自旋時間(自旋計數)或進入阻塞,如果 CPU 處于節(jié)電模式則停止自旋,自旋時間的最壞情況是CPU的存儲延遲(CPU A 存儲了一個數據,到CPU B得知這個數據直接的時間差),自旋時會適當放棄線程優(yōu)先級之間的差異。
到此這篇關于Java中的自旋鎖與適應性自旋鎖詳解的文章就介紹到這了,更多相關Java中的自旋鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java調用wsdl接口的兩種方法(axis和wsimport)
本文主要介紹了Java調用wsdl接口的兩種方法(axis和wsimport),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-03-03