Java中的synchronized鎖膨脹詳解
1. 基本概念
Java對象頭
Java對象的對象頭信息中的 Mark Word 主要是用來記錄對象的鎖信息的。
現在看一下 Mark Word 的對象頭信息。如下:
其實可以根據 mark word 的后3位就可以判斷出當前的鎖是屬于哪一種鎖。注意:表格中的正常鎖其實就是無鎖狀態(tài)了。
2. 幾種鎖以及原理
無鎖
正常創(chuàng)建的對象,狀態(tài)為無鎖。對象頭的Mark Word 中主要記錄了 對象的年齡,也就是經歷了多少次GC還存活下來。
偏向鎖
對象頭的Mark Word 中記錄的信息比正常鎖多的是 記錄了線程的信息,也就是線程的id。偏向鎖,字面意思就是比較偏心,偏向于某個線程。
偏向鎖加鎖原理
經過分析可以看到,thread 字段剛開始的時候為0。工作原理是 cas(thread,0,當前線程), 也就是判斷當前的 thread的值如果是0的話,就賦值為當前線程。如果cas 拿鎖失敗了,那么有倆種情況。
重入
當前線程已經是偏向鎖的持有者了,那么只需要判斷 thread 字段是否就是當前線程,如果是的話,其實就是當前線程已經拿到這把鎖了。那么此時就會在棧中創(chuàng)建lockRecord。因為棧是線程私有的。lockRecord 其實包含倆部分的內容,第一部分內容和鎖對象的 markword中的對象是完全一樣的。記為M區(qū)。第二部分是記錄了當前對象,也就是加鎖對象的指針。記為O區(qū)。在第一次加鎖的時候,是不需要創(chuàng)建 lockRecord的。只有在重入的時候,才需要創(chuàng)建 lockRecord的。
其它線程持有鎖
那么此時就會升級為輕量級鎖。
偏向鎖解鎖原理
解鎖過程非常簡單,只需要在當前線程的棧上刪除最近的lockRecord對象就可以了。因為重入的時候是不斷的創(chuàng)建這些lockRecord對象的。
輕量級鎖
主要是將鎖對象的Mark Word更新為指向Lock Record的指針,也就是鎖記錄。注意,這個鎖記錄是在棧上的。
輕量級鎖加鎖原理
線程在自己的棧楨中創(chuàng)建鎖記錄 LockRecord。
將鎖對象的對象頭中的MarkWord復制到線程的剛剛創(chuàng)建的鎖記錄中。
將鎖對象的對象頭的MarkWord替換為指向鎖記錄的指針。也就是進行cas操作。cas(ptr, null, lockRecord)。也就是如果對象頭的ptr指針如果為空,那么就賦值為當前的lockRecord對象。并將線程棧幀中的Lock Record里的owner指針指向Object的 Mark Word。如果賦值成功了,那么就可以認為加鎖成功了。
如果加鎖失敗了,也有倆種情況。
重入。
就是ptr 指向的是另外的一個lockRecord對象,但是也是當前線程創(chuàng)建的。也就是在當前線程的棧上是可以找到這個lockRecord對象。首先在棧上檢查是否可以找到這個鎖對象。如果可以找到,就是重入。如果是重入,那么就需要將鎖記錄中的Owner指針指向鎖對象。
其它線程持有鎖
那么就進入重量級鎖。
輕量級鎖解鎖原理
釋放鎖的時候,從棧上進行掃描,從后往前 找到最近的lockRecord,刪除。
重量級鎖
主要是指向一個monitor 對象。
分析如下: synchronized重量級鎖解析
3. 看一段程序,看一下鎖是如何進行演變的。
package org.example; import sun.misc.Unsafe; import java.lang.reflect.Field; /** * @author frank wy170862@alibaba-inc.com * @date 2020-02-24 */ public class A { public static void main(String[] args) throws Exception { test2(); } /** * 一、正常創(chuàng)建的對象,狀態(tài)為無鎖,觀察hashcode和age的變化。hashcode 的計算是惰性的。剛開始的時候,對象的hashcode是0。只有在計算了hashcode以后,才會將hashcode存儲到對象頭,下次取hashcode的時候,就可以直接從對象頭中取出hashcode了。 * * 鎖狀態(tài):無鎖,hashCode:0,age: 0 * --------------- * * 運行hashcode方法,得到hashcode:648129364 * 鎖狀態(tài):無鎖,hashCode:648129364,age: 0 * --------------- * * [GC (System.gc()) [PSYoungGen: 6568K->1095K(76288K)] 6568K->1103K(251392K), 0.0017301 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] * [Full GC (System.gc()) [PSYoungGen: 1095K->0K(76288K)] [ParOldGen: 8K->932K(175104K)] 1103K->932K(251392K), [Metaspace: 3084K->3084K(1056768K)], 0.0054386 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] * 運行一次gc,obj的age+1 * 鎖狀態(tài):無鎖,hashCode:648129364,age: 1 * --------------- * @throws Exception */ private static void test1() throws Exception { Object a = new Object(); printLockHeader(a); System.out.println("運行hashcode方法,得到hashcode:" + a.hashCode());; printLockHeader(a); System.gc(); System.out.println("運行一次gc,obj的age+1"); // sleep 1s 讓gc完成,但是不一定能100%觸發(fā)gc,可以配合添加運行參數 -XX:+PrintGCDetails,觀察確實gc了 Thread.sleep(1000); printLockHeader(a); } /** * 二、正常創(chuàng)建的對象,狀態(tài)為無鎖,無鎖狀態(tài)直接加鎖會變成輕量鎖 * * 鎖狀態(tài):無鎖,hashCode:0,age: 0 * --------------- * * 對a加鎖后 * 鎖狀態(tài):輕量級鎖,LockRecord地址:1c00010c6e28 * --------------- * @throws Exception */ private static void test2() throws Exception { Object a = new Object(); printLockHeader(a); synchronized (a){ System.out.println("對a加鎖后"); printLockHeader(a); } } /** * 三、程序啟動一定時間后,正常創(chuàng)建的對象,狀態(tài)為偏向鎖且thread為0,此時加鎖默認為偏向鎖 * 一段時間一般是幾秒,-XX:BiasedLockingStartupDelay=0可以指定默認就使用偏向鎖,而不是無鎖 * * 鎖狀態(tài):偏向鎖,thread:0,epoch: 0,age: 0 * --------------- * * 對a加鎖后 * 鎖狀態(tài):偏向鎖,thread:137069895700,epoch: 0,age: 0 * --------------- * * 偏向鎖重入后 * 鎖狀態(tài):偏向鎖,thread:137069895700,epoch: 0,age: 0 * --------------- * @throws Exception */ private static void test3() throws Exception { Thread.sleep(5*1000); Object a = new Object(); printLockHeader(a); synchronized (a){ System.out.println("對a加鎖后"); printLockHeader(a); System.out.println("偏向鎖重入后"); synchronized (a){ printLockHeader(a); } } } /** * 四、基于三,當另一個線程嘗試使用對象鎖的時候,升級為輕量鎖 * * 鎖狀態(tài):偏向鎖,thread:0,epoch: 0,age: 0 * --------------- * * 線程1對a加鎖后 * 鎖狀態(tài):偏向鎖,thread:137122299998,epoch: 0,age: 0 * --------------- * * 鎖釋放了 * 線程2對a加鎖后 * 鎖狀態(tài):輕量級鎖,LockRecord地址:1c00015e6a18 * --------------- * @throws Exception */ private static void test4() throws Exception { Thread.sleep(5*1000); Object a = new Object(); printLockHeader(a); new Thread( ()->{ synchronized (a){ System.out.println("線程1對a加鎖后"); try { printLockHeader(a); } catch (Exception e) { e.printStackTrace(); } } try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } ).start(); // 中間sleep1s,保證鎖釋放掉,使兩個線程不會有競爭關系 Thread.sleep(1000); System.out.println("鎖釋放了"); new Thread( ()->{ synchronized (a){ System.out.println("線程2對a加鎖后"); try { printLockHeader(a); } catch (Exception e) { e.printStackTrace(); } } } ).start(); } /** * 五、基于四,當產生競爭的時候偏向鎖直接升級為重量級鎖 * * 鎖狀態(tài):偏向鎖,thread:0,epoch: 0,age: 0 * --------------- * * 線程1對a加鎖后 * 鎖狀態(tài):偏向鎖,thread:137025283340,epoch: 0,age: 0 * --------------- * * 線程2對a加鎖后 * 鎖狀態(tài):重量級鎖,Monitor地址:1fe758800a02 * --------------- * @throws Exception */ private static void test5() throws Exception { Thread.sleep(5*1000); Object a = new Object(); printLockHeader(a); new Thread( ()->{ synchronized (a){ System.out.println("線程1對a加鎖后"); try { printLockHeader(a); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } } ).start(); new Thread( ()->{ synchronized (a){ System.out.println("線程2對a加鎖后"); try { printLockHeader(a); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } } ).start(); } /** * 六、四+五 演示偏向-輕量-重量過程 * * 鎖狀態(tài):偏向鎖,thread:0,epoch: 0,age: 0 * --------------- * * 線程1對a加鎖后 * 鎖狀態(tài):偏向鎖,thread:137272648594,epoch: 0,age: 0 * --------------- * * 鎖釋放 * 線程2對a加鎖后 * 鎖狀態(tài):輕量級鎖,LockRecord地址:1c0001b43e18 * --------------- * * 鎖釋放 * 線程3對a加鎖后 * 鎖狀態(tài):輕量級鎖,LockRecord地址:1c0001b43e18 * --------------- * * 線程4對a加鎖后 * 鎖狀態(tài):重量級鎖,Monitor地址:1ff616600f02 * --------------- * @throws Exception */ private static void test6() throws Exception { Thread.sleep(5*1000); Object a = new Object(); printLockHeader(a); new Thread( ()->{ synchronized (a){ System.out.println("線程1對a加鎖后"); try { printLockHeader(a); } catch (Exception e) { e.printStackTrace(); } } try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } ).start(); // 中間sleep1s,使線程不會有競爭關系 Thread.sleep(1000); System.out.println("鎖釋放"); new Thread( ()->{ synchronized (a){ System.out.println("線程2對a加鎖后"); try { printLockHeader(a); } catch (Exception e) { e.printStackTrace(); } } } ).start(); // 中間sleep1s,使線程不會有競爭關系 Thread.sleep(1000); System.out.println("鎖釋放"); new Thread( ()->{ synchronized (a){ System.out.println("線程3對a加鎖后"); try { printLockHeader(a); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } } ).start(); // 此時不再sleep,使線程必然發(fā)生競爭,升級為重量級鎖 new Thread( ()->{ synchronized (a){ System.out.println("線程4對a加鎖后"); try { printLockHeader(a); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } } ).start(); } private static Unsafe getUnsafe() throws Exception { Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); Field field = unsafeClass.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } private static void printLockHeader(Object obj) throws Exception { Unsafe us = getUnsafe(); StringBuilder sb = new StringBuilder(); int status = us.getByte(obj, 0L) & 0B11; // 0 輕量級 1 無鎖或偏向 2 重量級 3 GC標記 switch (status){ case 0: // ptr_to_lock_record:62|lock:2 long ptrToLockRecord = (byteMod(us.getByte(obj, 0L))>>2) + (byteMod(us.getByte(obj, 1L))<<6) + (byteMod(us.getByte(obj, 2L))<<14) + (byteMod(us.getByte(obj, 3L))<<22) + (byteMod(us.getByte(obj, 4L))<<30) + (byteMod(us.getByte(obj, 5L))<<38) + (byteMod(us.getByte(obj, 6L))<<46) + (byteMod(us.getByte(obj, 7L))<<54); sb.append("鎖狀態(tài):輕量級鎖,LockRecord地址:") .append(Long.toHexString(ptrToLockRecord)) ; break; case 1: boolean biased = (us.getByte(obj, 0L)&4) == 4; if(!biased){ // unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 int hashCode = (int)(byteMod(us.getByte(obj, 1L)) + (byteMod(us.getByte(obj, 2L))<<8) + (byteMod(us.getByte(obj, 3L))<<16) + ((byteMod(us.getByte(obj, 4L))&Integer.MAX_VALUE) <<24)) ; int age = (us.getByte(obj,0L)>>3)&0B1111; sb.append("鎖狀態(tài):無鎖,hashCode:") .append(hashCode) .append(",age: ") .append(age); }else{ //thread:54|epoch:2|unused:1| age:4 | biased_lock:1 | lock:2 long thread = (byteMod(us.getByte(obj, 1L))>>2) + (byteMod(us.getByte(obj, 2L))<<6) + (byteMod(us.getByte(obj, 3L))<<14) + (byteMod(us.getByte(obj, 4L))<<22) + (byteMod(us.getByte(obj, 5L))<<30) + (byteMod(us.getByte(obj, 6L))<<38) + (byteMod(us.getByte(obj, 7L))<<46); ; int epoch = us.getByte(obj, 1L) & 0B11; int age = (us.getByte(obj,0L)>>3)&0B1111; sb.append("鎖狀態(tài):偏向鎖,thread:") .append(thread) .append(",epoch: ") .append(epoch) .append(",age: ") .append(age); } break; case 2: // ptr_to_heavyweight_monitor:62| lock:2 long ptrToMonitor = (byteMod(us.getByte(obj, 0L))>>2) + (byteMod(us.getByte(obj, 1L))<<6) + (byteMod(us.getByte(obj, 2L))<<14) + (byteMod(us.getByte(obj, 3L))<<22) + (byteMod(us.getByte(obj, 4L))<<30) + (byteMod(us.getByte(obj, 5L))<<38) + (byteMod(us.getByte(obj, 6L))<<46) + (byteMod(us.getByte(obj, 7L))<<54); sb.append("鎖狀態(tài):重量級鎖,Monitor地址:") .append(Long.toHexString(ptrToMonitor)) ; break; case 3: sb.append("鎖狀態(tài):GC標記"); break; default: break; } if(obj instanceof Object[]){ int arrLen = us.getInt(obj, 3L); sb.append("對象為數組類型,數組長度:") .append(arrLen); } sb.append("\n").append("---------------").append("\n"); System.out.println(sb.toString()); } private static long byteMod(byte b){ if(b>=0){ return b; } return b + 256; } }
4. 總結
剛開始,程序開始運行的時候,創(chuàng)建的對象都屬于無鎖對象。程序運行一段時間后,一段時間一般指的是4秒鐘,創(chuàng)建的對象屬于偏向鎖對象。無鎖狀態(tài)下直接加鎖會變?yōu)檩p量級鎖。偏向鎖狀態(tài)下的對象,加鎖的話,會對當前線程有一個偏向。如果此時再有另外的一個線程過來申請鎖,那么就會升級為輕量級鎖對象。輕量級鎖下,如果存在競爭,那么一定會升級為重量級鎖。當然,偏向鎖狀態(tài)下,如果存在競爭,也會升級為重量級鎖。
總結起來就是:如果存在競爭,那么一定會升級為重量級鎖。如果存在另外一個線程想要拿到這把鎖,就會升級為輕量級鎖。偏向鎖和輕量級鎖的區(qū)別是:輕量級鎖是可以另外一個線程來拿鎖,也就是一個線程釋放掉鎖以后,另外一個線程可以來拿鎖,這就是輕量級鎖。如果一個線程釋放掉鎖以后,另外一個線程拿不到這把鎖,那么就屬于偏向鎖。
到此這篇關于Java中的synchronized鎖膨脹詳解的文章就介紹到這了,更多相關synchronized鎖膨脹內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Cloud Gateway 服務網關快速實現解析
這篇文章主要介紹了Spring Cloud Gateway 服務網關快速實現解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-08-08Spring?boot?easyexcel?實現復合數據導出、按模塊導出功能
這篇文章主要介紹了Spring?boot?easyexcel?實現復合數據導出、按模塊導出,實現思路流程是準備一個導出基礎填充模板,默認填充key,本文給大家介紹的非常詳細,需要的朋友可以參考下2023-09-09