什么是鎖升級(鎖膨脹)?
JVM優化synchronized的運行機制,當JVM檢測到不同的競爭狀態時,就會根據需要自動切換到合適的鎖,這種切換就是鎖的升級。升級是不可逆的,也就是說只能從低到高,也就是偏向-->輕量級-->重量級,不能夠降級
鎖級別:無鎖->偏向鎖->輕量級鎖->重量級鎖
java對象頭
synchronized用的鎖存在Java對象頭里,Java對象頭里的Mark Word默認存儲對象的HashCode、分代年齡和鎖標記位。在運行期間,Mark Word里存儲的數據會隨着鎖標志位的變化而變化。32位JVM的Mark Word可能變化存儲為以下5種數據:
CAS
compareAndSwap,比較並替換,是一種實現並發算法時常用到的技術CAS需要有3個操作數:內存地址V,舊的預期值A,即將要更新的目標值B;比如你要操作一個變量,他的值為A,你希望將他修改為B,這期間不會進行加鎖,當你在修改的時候,你發現值仍舊是A,然后將它修改為B,如果此時值被其他線程修改了,變成了C,那么將不會進行值B的寫入操作,這就是CAS的核心理論,通過這樣的操作可以實現邏輯上的一種“加鎖”,避免了真正去加鎖。
實例自增代碼:
public final int incrementAndGet() { for (; ; ) { //自旋 int current = get(); //舊值 int next = current + 1; //新值 if (compareAndSet(current, next)) //如果舊的預期值與內存中的值一致,那么將新值進行賦值,否則繼續自旋 return next; } }
偏向鎖
當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀的鎖記錄里存儲偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需測試Mark Word里線程ID是否為當前線程。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要判斷偏向鎖的標識。如果標識被設置為0(表示當前是無鎖狀態),則使用CAS競爭鎖;如果標識設置成1(表示當前是偏向鎖狀態),則嘗試使用CAS將對象頭的偏向鎖指向當前線程,觸發偏向鎖的撤銷。偏向鎖只有在競爭出現才會釋放鎖。當其他線程嘗試競爭偏向鎖時,程序到達全局安全點后(沒有正在執行的代碼),它會查看Java對象頭中記錄的線程是否存活,如果沒有存活,那么鎖對象被重置為無鎖狀態,其它線程可以競爭將其設置為偏向鎖;如果存活,那么立刻查找該線程的棧幀信息,如果還是需要繼續持有這個鎖對象,那么暫停當前線程,撤銷偏向鎖,升級為輕量級鎖,如果線程1不再使用該鎖對象,那么將鎖對象狀態設為無鎖狀態,重新偏向新的線程。
偏向鎖應用的場景是一個同步代碼塊只有一個線程頻繁訪問,使用偏向鎖,就不需要頻繁使用CAS獲取鎖和釋放鎖,只需要簡單判斷對象頭中記錄的偏向鎖的線程ID是否是當期線程的就可以了,所以偏向鎖在這種場景下可以大大提升效率。
輕量級鎖
線程在執行同步塊之前,JVM會先在當前線程的棧幀中創建用於存儲鎖記錄的空間,並將對象頭的MarkWord復制到鎖記錄中,即Displaced Mark Word。然后線程會嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖。如果失敗,表示其他線程在競爭鎖,當前線程使用自旋來獲取鎖。當自旋次數達到一定次數時,鎖就會升級為重量級鎖。
當線程存在競爭時,偏向鎖的效率就會降低,因為當多條線程競爭同一個偏向鎖時,會頻繁產生偏向鎖的撤銷,所以此時應該升級為輕量級鎖,輕量級鎖當線程競爭鎖失敗時,線程不會阻塞進入自旋,繼續獲取鎖,當競爭非常激烈時,持續自旋而獲取不到鎖會消耗大量CPU資源,此時就會升級為重量級鎖,重量級鎖當獲取鎖失敗線程會阻塞,重量級鎖的缺點是線程上下文會頻繁的切換。
synchronized優化-鎖消除
消除鎖是虛擬機另外一種鎖的優化,這種優化更徹底,Java虛擬機在JIT編譯時(可以簡單理解為當某段代碼即將第一次被執行時進行編譯,又稱即時編譯),通過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖,通過這種方式消除沒有必要的鎖,可以節省毫無意義的請求鎖時間,如下StringBuffer的append是一個同步方法,但是在add方法中的StringBuffer屬於一個局部變量,並且不會被其他線程所使用,因此StringBuffer不可能存在共享資源競爭的情景,JVM會自動將其鎖消除。