java-鎖膨脹的過程
先來看個奇怪的demo
public class A { int i=0; // boolean flag =false; public synchronized void parse(){ i++; JOLExample6.countDownLatch.countDown(); } }
睡眠5秒,測試
public class JOLExample3 { static A a; public static void main(String[] args) throws Exception { Thread.sleep(5000); a= new A(); //a.hashCode(); out.println("befor lock"); out.println(ClassLayout.parseInstance(a).toPrintable());//無鎖:偏向鎖? synchronized (a){ out.println("lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } out.println("after lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); } }
我注釋的那行程式碼是什麼鎖?看下結果
可以看出,沒有執行緒持有鎖的時候,是可偏向狀態
然後我們把睡眠的程式碼注釋掉,再測試一下
//Thread.sleep(5000);
看下結果
再看個兩個執行緒的demo
首先是兩個執行緒交替執行:
public class JOLExample10 { static A a; public static void main(String[] args) throws Exception { a= new A(); Thread t1 = new Thread(){ @Override public void run() { synchronized (a){ out.println("t1 lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } }; t1.start(); Thread.sleep(10000);//睡眠10秒,讓main執行緒和t1執行緒交替執行 synchronized (a){//a b c c+++ out.println("main lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } out.println("after lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); } }
看下結果
可以看出,交替執行時,是輕量鎖
我們把睡眠的程式碼注釋掉
//Thread.sleep(5000);//睡眠10秒,讓main執行緒和t1執行緒交替執行
再次測試,
public class JOLExample10 { static A a; public static void main(String[] args) throws Exception { a= new A(); Thread t1 = new Thread(){ @Override public void run() { synchronized (a){ out.println("t1 lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } }; t1.start(); //Thread.sleep(5000);//睡眠10秒,讓main執行緒和t1執行緒交替執行 synchronized (a){//a b c c+++ out.println("main lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } Thread.sleep(5000);//睡眠10秒 out.println("after lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); } }
看下結果
自旋
自旋一段時間,可以理解為空轉,時間很短,具體時間需要看jvm源碼,如果在自旋時間內拿到了鎖,就不再膨脹,如果還是拿不到鎖,則膨脹為重量鎖,如下
public static void main(String[] args) throws Exception { a= new A(); Thread t1 = new Thread(){ @Override public void run() { synchronized (a){ out.println("t1 lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } }; t1.start(); Thread.sleep(1670);//睡眠10秒,讓main執行緒和t1執行緒交替執行 synchronized (a){//自旋一段時間,可以理解為時間很短,具體時間需要看jvm源碼,如果在自旋時間內拿到了鎖,就不再膨脹,如果還是拿不到鎖,則膨脹為重量鎖 out.println("main lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } Thread thread2 = new Thread(){ @Override public void run() { synchronized (a){ out.println("t2 lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } }; thread2.start(); /*Thread.sleep(10); synchronized (a){ out.println("main lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); }*/ //Thread.sleep(5000);//睡眠10秒,讓main執行緒和t1執行緒交替執行 out.println("after lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); }
如果調用wait方法,則立即變成重量鎖
看下demo
public class JOLExample11 { static A a; public static void main(String[] args) throws Exception { //Thread.sleep(5000); a = new A(); out.println("befre lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); Thread t1= new Thread(){ public void run() { synchronized (a){ try { synchronized (a) { System.out.println("before wait"); out.println(ClassLayout.parseInstance(a).toPrintable()); a.wait();//如果調用wait方法,則立即變成重量鎖 System.out.println(" after wait"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } catch (InterruptedException e) { e.printStackTrace(); } } } }; t1.start(); Thread.sleep(7000); synchronized (a) { a.notifyAll(); } } }
看下結果
我們再看個synchronized膨脹的奇怪特性
讓偏向鎖無延遲啟動
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
public class JOLExample12 { static List<A> list = new ArrayList<A>(); public static void main(String[] args) throws Exception { Thread t1 = new Thread() { public void run() { for (int i=0;i<10;i++){ A a = new A(); synchronized (a){ System.out.println("111111"); list.add(a); } } } }; t1.start(); t1.join(); out.println("befre t2"); //偏向 out.println(ClassLayout.parseInstance(list.get(1)).toPrintable()); Thread t2 = new Thread() { int k=0; public void run() { for(A a:list){ synchronized (a){ System.out.println("22222"); if (k==4){ out.println("t2 ing"); //輕量鎖 out.println(ClassLayout.parseInstance(a).toPrintable()); } } k++; } } }; t2.start(); } }
t1執行緒new10個對象,t2執行緒取第五個,看下結果
我們把new對象的數量改一下,改成20個,再來試一下
public class JOLExample12 { static List<A> list = new ArrayList<A>(); public static void main(String[] args) throws Exception { Thread t1 = new Thread() { public void run() { for (int i=0;i<20;i++){ A a = new A(); synchronized (a){ System.out.println("111111"); list.add(a); } } } }; t1.start(); t1.join(); out.println("befre t2"); //偏向 out.println(ClassLayout.parseInstance(list.get(1)).toPrintable()); Thread t2 = new Thread() { int k=0; public void run() { for(A a:list){ synchronized (a){ System.out.println("22222"); if (k==19){ out.println("t2 ing"); //輕量鎖 out.println(ClassLayout.parseInstance(a).toPrintable()); } } k++; } } }; t2.start(); } }
這裡我們取第20個對象,查看對象頭資訊
我們可以看到,居然重偏向了,這裡是jvm做的優化,20次以後就會沖偏向,小於20次時膨脹為輕量鎖
這裡我們稱之為批量偏向,下面我們看下這個的原理
最後總結一下:輕量鎖釋放的時候將mark word重置為無鎖狀態,附上網路上的圖