一、Synchronized作用
(1)確保線程互斥的訪問同步代碼
(2)保證共享變量的修改能夠及時可見
(3)有效解決重排序問題。(Synchronized同步中的代碼JVM不會輕易優化重排序)
二、Synchronized常見用法分析
1.修飾普通方法
1 package lock; 2 3 /** 4 * 5 * @ClassName:SynchronizedDemo 6 * @Description:測試synchronized 7 * @author diandian.zhang 8 * @date 2017年4月1日下午7:02:34 9 */ 10 public class SynchronizedDemo { 11 12 public synchronized void method1(){ 13 System.out.println("進入方法1"); 14 try { 15 System.out.println("方法1執行"); 16 Thread.sleep(3000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 System.out.println("方法1 end"); 21 } 22 23 public synchronized void method2(){ 24 System.out.println("進入方法2"); 25 try { 26 System.out.println("方法2執行"); 27 Thread.sleep(000); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 System.out.println("方法2 end"); 32 } 33 34 public static void main(String[] args) { 35 SynchronizedDemo demo = new SynchronizedDemo(); 36 new Thread(new Runnable() { 37 @Override 38 public void run() { 39 demo.method1(); 40 } 41 }).start(); 42 43 new Thread(new Runnable() { 44 @Override 45 public void run() { 46 demo.method2(); 47 } 48 }).start(); 49 } 50 }
結果:
1 進入方法1 2 方法1執行 3 方法1 end 4 進入方法2 5 方法2執行 6 方法2 end
可見:修飾普通方法,線程2需要等待線程1的method1執行完成才能開始執行method2方法,方法級別串行執行。
2.修飾靜態方法
1 package lock; 2 3 /** 4 * 5 * @ClassName:SynchronizedDemo2 6 * @Description:修飾靜態方法 7 * @author diandian.zhang 8 * @date 2017年4月5日上午10:48:56 9 */ 10 public class SynchronizedDemo2 { 11 12 public static synchronized void method1(){ 13 System.out.println("進入方法1"); 14 try { 15 System.out.println("方法1執行"); 16 Thread.sleep(3000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 System.out.println("方法1 end"); 21 } 22 23 public static synchronized void method2(){ 24 System.out.println("進入方法2"); 25 try { 26 System.out.println("方法2執行"); 27 Thread.sleep(1000); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 System.out.println("方法2 end"); 32 } 33 34 public static void main(String[] args) { 35 new Thread(new Runnable() { 36 @Override 37 public void run() { 38 SynchronizedDemo2.method1(); 39 } 40 }).start(); 41 42 new Thread(new Runnable() { 43 @Override 44 public void run() { 45 SynchronizedDemo2.method2(); 46 } 47 }).start(); 48 } 49 }
運行結果:
1 進入方法1 2 方法1執行 3 進入方法2 4 方法1 end 5 方法2執行 6 方法2 end
可見:修飾靜態方法,本質是對類的同步,任何實例調用方法,都類級別串行(每個實例不一定串行)執行。
3.修飾代碼塊
1 package lock; 2 3 /** 4 * 5 * @ClassName:SynchronizedDemo3 6 * @Description:Synchronized修飾代碼塊 7 * @author diandian.zhang 8 * @date 2017年4月5日上午10:49:50 9 */ 10 public class SynchronizedDemo3 { 11 12 public void method1(){ 13 System.out.println("進入方法1"); 14 try { 15 synchronized (this) { 16 System.out.println("方法1執行"); 17 Thread.sleep(3000); 18 } 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 System.out.println("方法1 end"); 23 } 24 25 public void method2(){ 26 System.out.println("進入方法2"); 27 try { 28 synchronized (this) { 29 System.out.println("方法2執行"); 30 Thread.sleep(1000); 31 } 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 System.out.println("方法2 end"); 36 } 37 38 public static void main(String[] args) { 39 SynchronizedDemo3 demo = new SynchronizedDemo3(); 40 new Thread(new Runnable() { 41 @Override 42 public void run() { 43 demo.method1(); 44 } 45 }).start(); 46 47 new Thread(new Runnable() { 48 @Override 49 public void run() { 50 demo.method2(); 51 } 52 }).start(); 53 } 54 }
運行結果:
1 進入方法1 2 方法1執行 3 進入方法2 4 方法1 end 5 方法2執行 6 方法2 end
可見,修飾代碼塊,只鎖住代碼塊的執行順序。代碼塊級別串行。(例如上面的方法1和方法2沒能串行,因為鎖住的是同一個對象,但是同步代碼塊只包住了方法中的一部分)
三、Synchronized 原理
實際上,JVM只區分兩種不同用法 1.修飾代碼塊 2.修飾方法。什么,你不信?好吧,上SE8規范:http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.14
上圖中,紅框框中說明了,1.monitorenter+monitorexit(上圖中的onlyMe方法中同步代碼塊) 2.修飾方法
這一切都是規范說的,下面自測一下吧~
下面,我們通過JDK自帶工具反編譯,查看包含java字節碼的指令。
3.1 synchronized修飾代碼塊
java源碼如下:
1 public class SynchronizedDemo { 2 public void method (){ 3 synchronized (this) { 4 System.out.println("method 1 start!!!!"); 5 } 6 } 7 }
javac -encoding utf-8 SynchronizedDemo.java 編譯生成class 后,javap -c 反編譯一下,看指令:
這里着重分析2個monitorenter、monitorexit這兩個指令。這里以JSE8位為准,查到屬於JVM指令集。官網各種API、JVM規范,指令等,傳送門:http://docs.oracle.com/javase/8/docs/。
1.monitorenter監視器准入指令
主要看上圖中Description:
每個對象有一個監視器鎖(monitor)。當monitor被占用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:
1.如果monitor的進入數為0,則該線程進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。
2.如果線程已經占有該monitor,只是重新進入,則進入monitor的進入數加1.
3.’如果其他線程已經占用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權。
2.monitorexit監視器釋放指令
主要看上圖中Description:
1.執行monitorexit的線程必須是objectref所對應的monitor的所有者。
2.指令執行時,monitor的進入數減1,如果減1后進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。
3.2 synchronized修飾方法
java源碼如下:
1 package lock; 2 3 /** 4 * 5 * @ClassName:SynchronizedDemo0 6 * @Description: Synchronized修飾方法 7 * @author diandian.zhang 8 * @date 2017年4月5日下午6:18:12 9 */ 10 public class SynchronizedDemo0 { 11 public synchronized void method (){ 12 System.out.println("method start!!!!"); 13 } 14 }
javap -v 查看class文件,發現method上加了同步標簽,本質上還是monitor
========附言分割線=====
附:全文參考http://www.cnblogs.com/paddix/p/5367116.html