在JAVA多線程編程中,將需要並發執行的代碼放在Thread類的run方法里面,然后創建多個Thread類的對象,調用start()方法,線程啟動執行。
當某段代碼需要互斥時,可以用 synchronized 關鍵字修飾,這里討論 synchronized 關鍵字修飾方法時,是如何互斥的。
synchronized 修飾方法時鎖定的是調用該方法的對象。它並不能使調用該方法的多個對象在執行順序上互斥。
下面舉個具體的例子說明:
Test.java 通過 implements Runnable 成為一個線程類,它有一個MethodSync實例變量,這樣,每當實例化一個Test對象時(相當於創建一個線程)就會初始化一個相應的MethodSync對象。然后在Test類的run()方法里面調用 synchronized 修飾的方法。
Test.java
1 public class Test implements Runnable{ 2 private String name; 3 // private static MethodSync methodSync = new MethodSync(); 4 private MethodSync methodSync = new MethodSync(); 5 6 public Test(String name){ 7 this.name = name; 8 } 9 10 @Override 11 public void run() { 12 methodSync.method(name); 13 } 14 15 public static void main(String[] args) { 16 Thread t1 = new Thread(new Test("test 1")); 17 Thread t2 = new Thread(new Test("test 2")); 18 t1.start(); 19 t2.start(); 20 } 21 }
上面創建了二個Test類的對象,相當於啟動了兩個線程, 這兩個線程將會並發地執行它們的run方法中的代碼。
MethodSync.java ,該類只擁有一個用來測試的 synchronized 方法
1 public class MethodSync { 2 3 /* 4 * @Task : 測試 synchronized 修飾方法時鎖定的是調用該方法的對象 5 * @param name 線程的標記名稱 6 */ 7 public synchronized void method(String name){ 8 System.out.println(name + " Start a sync method"); 9 try{ 10 Thread.sleep(300); 11 }catch(InterruptedException e){} 12 System.out.println(name + " End the sync method"); 13 } 14 }
先看執行結果:
test1 先於 test2 執行 同步方法,但是卻后於 test2 結束。這里並沒有達到互斥的效果!原因是:MethodSync是實例變量,每次創建一個Test對象就會創建一個MethodSync對象, synchronized 只會鎖定調用method()方法的那個MethodSync對象,而這里創建的兩個線程分別擁有兩個不同的MethodSync對象,它們調用method方法時就沒有互斥關系。
當把Test.java 中的MethodSync 變量 用 static 來修飾時,執行結果如下:
這里,正確實現了同步作用。原因如下:這里也創建了二個線程(Test 對象),但是每個Test對象共享MethodSync 變量,也即只有一個MethodSync 變量在兩個線程中執行 method方法,這樣兩個線程在執行到method 方法這段代碼時就會形成互斥。
2018.12.13更新:
記錄一下我對synchronized關鍵字的理解:
1,相對於ReentrantLock而言,synchronized鎖是重量級鎖,重量級體現在活躍性差一點。synchronized鎖是內置鎖,意味着JVM能基於synchronized鎖做一些優化:比如增加鎖的粒度(鎖粗化)、鎖消除。
2,在synchronized鎖上阻塞的線程是不可中斷的:線程A獲得了synchronized鎖,當線程B也去獲取synchronized鎖時會被阻塞。而且,線程B無法被其他線程中斷(不可中斷的阻塞),而ReentrantLock鎖能實現可中斷的阻塞。
3,synchronized鎖釋放是自動的,當線程執行退出synchronized鎖保護的同步代碼塊時,會自動釋放synchronized鎖。而ReentrantLock需要顯示地釋放:即在try-finally塊中釋放鎖。
4,線程在競爭synchronized鎖時是非公平的:假設synchronized鎖目前被線程A占有,線程B請求鎖未果,被放入隊列中,線程C請求鎖未果,也被 放入隊列中,線程D也來請求鎖,恰好此時線程A將鎖釋放了,那么線程D將跳過隊列中所有的等待線程(即:線程B和線程C)並獲得這個鎖。
而ReentrantLock能夠實現鎖的公平性。
5,synchronized鎖是讀寫互斥並且 讀讀也互斥,ReentrantReadWriteLock 分為讀鎖和寫鎖,而讀鎖可以同時被多個線程持有,適合於讀多寫少場景的並發。