public class SynMethod { private static final Object staticLockObj = new Object(); /** * 對象鎖,代碼級別,同一對象爭用該鎖,this爲SynMethod實例,synchronized的鎖綁定在this對象上 */ public void method1() { synchronized (this) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } } /** * 對象鎖,方法級別,同一對象爭用該鎖,普通(非靜態)方法,synchronized的鎖綁定在調用該方法的對象上,與上一個寫法含義一致 */ public synchronized void method2() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } /** * 對象鎖,代碼級別,同一類爭用該鎖,綁定在staticLockObj上,不一樣SynMethod實例,擁有同一個staticLockObj對象 */ public void method3() { synchronized (staticLockObj) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } } /** * 類鎖,代碼級別,同一類爭用該鎖 */ public void method4() { synchronized (SynMethod.class) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } } /** * 類鎖,方法級別,同一類爭用該鎖,synchronized的鎖綁定在SynMethod.class上 */ public static synchronized void staticMethod() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } }
下面咱們來測試一下(因爲同步運行結果收到線程調度等各類影響,對於沒法達到同步效果的狀況,須要多運行幾回)html
測試狀況1java
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method1(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t1.method1(); } }, "B"); ta.start(); tb.start(); } }
運行結果:編程
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
兩個線程運行了同一個對象t1的同一個public方法method1,這個方法在t1對象上同步,因此實現了同步的效果緩存
測試狀況2安全
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method1(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t1.method2(); } }, "B"); ta.start(); tb.start(); } }
運行結果:併發
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
兩個線程運行同一個對象t1的不一樣的方法method1和method2方法,可是這兩個方法是使用同一個對象t1上進行同步的,因此實現同步的效果,側面印證了這兩種寫法的一致性。app
測試狀況3:ide
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method3(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t2.method3(); } }, "B"); ta.start(); tb.start(); } }
運行結果:oop
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
此次兩個線程運行了不一樣的類對象t1和t2的同一個方法method3,這個方法是在一個靜態對象上同步,這個靜態變量是在這個類的全部實例上共享的,因此也是達到了同步的效果測試
測試狀況4:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method2(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t1.method3(); } }, "B"); ta.start(); tb.start(); } }
運行結果:
A synchronized loop 0 B synchronized loop 0 A synchronized loop 1 B synchronized loop 1 A synchronized loop 2 B synchronized loop 2 B synchronized loop 3 A synchronized loop 3 B synchronized loop 4 A synchronized loop 4
此次是兩個線程運行了同一個對象t1的method2和method3方法,這個方法分別在t1對象和SynMethod類的靜態對象上同步,因此達到同步效果
測試狀況5:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method4(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t2.method4(); } }, "B"); ta.start(); tb.start(); } }
運行結果:
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
兩個線程運行了不一樣對象t1和t2的同一個方法method4,該方法是在SynMethod類上同步,實現了同步效果
測試狀況6:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method4(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { SynMethod.staticMethod(); } }, "B"); ta.start(); tb.start(); } }
運行結果:
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
兩個線程分別運行了對象t1的method4和靜態方法staticMethod,這個兩個方法都在SynMethod類上同步,實現了同步的效果。
測試狀況7:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method4(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t2.method3(); } }, "B"); ta.start(); tb.start(); } }
運行結果:
A synchronized loop 0 B synchronized loop 0 A synchronized loop 1 B synchronized loop 1 A synchronized loop 2 B synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 3 B synchronized loop 4
此次兩個線程運行了兩個對象的method3和method4發放,這個兩個方法分別在SynMethod類和SynMethod類的靜態對象上同步,因此沒有達到同步效果
測試狀況8:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method4(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t2.method2(); } }, "B"); ta.start(); tb.start(); } }
運行結果:
A synchronized loop 0 B synchronized loop 0 A synchronized loop 1 B synchronized loop 1 A synchronized loop 2 B synchronized loop 2 A synchronized loop 3 B synchronized loop 3 A synchronized loop 4 B synchronized loop 4
此次兩個線程運行了兩個對象的method4和method2方法,這兩個方法分別在SynMethod類和對象t2上同步,因此沒有達到同步效果。
使用總結:雖然上面說的狀況比較多,可是從同步對象的角度,同步的場景只用三個,一個是SynMethod實例(能夠多個),SynMethod的靜態對象(共享)和SynMethod類(一個),只要是在同一個對象上同步,這個對象能夠是實例對象,能夠是靜態對象,能夠是類對象,那麼就能夠實現同步效果,不然沒法達到同步,這也與synchronized設計的初衷一致。
2.實現原理
首先咱們將SynMethod編譯一下(命令:javac SynMethod.java),獲得.class文件SynMethod.class,再經過反編譯命令(javap -c SynMethod.class)
Compiled from "SynMethod.java" public class concurrent.SynMethod { public concurrent.SynMethod(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void method1(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: iconst_0 5: istore_2 6: iload_2 7: iconst_5 8: if_icmpge 51 11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 14: new #3 // class java/lang/StringBuilder 17: dup 18: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 21: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 24: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 27: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 30: ldc #8 // String synchronized loop 32: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 35: iload_2 36: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 39: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 42: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 45: iinc 2, 1 48: goto 6 51: aload_1 52: monitorexit 53: goto 61 56: astore_3 57: aload_1 58: monitorexit 59: aload_3 60: athrow 61: return Exception table: from to target type 4 53 56 any 56 59 56 any public synchronized void method2(); Code: 0: iconst_0 1: istore_1 2: iload_1 3: iconst_5 4: if_icmpge 47 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 10: new #3 // class java/lang/StringBuilder 13: dup 14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 17: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 20: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 23: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 26: ldc #8 // String synchronized loop 28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: iload_1 32: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 35: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 38: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 41: iinc 1, 1 44: goto 2 47: return public void method3(); Code: 0: getstatic #12 // Field staticLockObj:Ljava/lang/Object; 3: dup 4: astore_1 5: monitorenter 6: iconst_0 7: istore_2 8: iload_2 9: iconst_5 10: if_icmpge 53 13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 16: new #3 // class java/lang/StringBuilder 19: dup 20: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 23: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 26: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: ldc #8 // String synchronized loop 34: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 37: iload_2 38: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 41: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 44: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 47: iinc 2, 1 50: goto 8 53: aload_1 54: monitorexit 55: goto 63 58: astore_3 59: aload_1 60: monitorexit 61: aload_3 62: athrow 63: return Exception table: from to target type 6 55 58 any 58 61 58 any public void method4(); Code: 0: ldc_w #13 // class concurrent/SynMethod 3: dup 4: astore_1 5: monitorenter 6: iconst_0 7: istore_2 8: iload_2 9: iconst_5 10: if_icmpge 53 13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 16: new #3 // class java/lang/StringBuilder 19: dup 20: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 23: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 26: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: ldc #8 // String synchronized loop 34: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 37: iload_2 38: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 41: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 44: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 47: iinc 2, 1 50: goto 8 53: aload_1 54: monitorexit 55: goto 63 58: astore_3 59: aload_1 60: monitorexit 61: aload_3 62: athrow 63: return Exception table: from to target type 6 55 58 any 58 61 58 any public static synchronized void staticMethod(); Code: 0: iconst_0 1: istore_0 2: iload_0 3: iconst_5 4: if_icmpge 47 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 10: new #3 // class java/lang/StringBuilder 13: dup 14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 17: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 20: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 23: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 26: ldc #8 // String synchronized loop 28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: iload_0 32: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 35: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 38: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 41: iinc 0, 1 44: goto 2 47: return static {}; Code: 0: new #14 // class java/lang/Object 3: dup 4: invokespecial #1 // Method java/lang/Object."<init>":()V 7: putstatic #12 // Field staticLockObj:Ljava/lang/Object; 10: return }
經過反編譯出來的代碼能夠看到,方法級別的synchronized同步使用了monitorenter和monitorexit這個同步命令,而monitorexit出現了兩次,猜想是因爲異常處理的須要
monitorenter和monitorexit這兩個命令的解釋參考JVM規範:
monitorenter :
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
這段話的大概意思爲:
每一個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:
一、若是monitor的進入數爲0,則該線程進入monitor,而後將進入數設置爲1,該線程即爲monitor的全部者。
二、若是線程已經佔有該monitor,只是從新進入,則進入monitor的進入數加1.
3.若是其餘線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再從新嘗試獲取monitor的全部權。
monitorexit:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
這段話的大概意思爲:
執行monitorexit的線程必須是objectref所對應的monitor的全部者。指令執行時,monitor的進入數減1,若是減1後進入數爲0,那線程退出monitor,再也不是這個monitor的全部者。其餘被這個monitor阻塞的線程能夠嘗試去獲取這個 monitor 的全部權。