本篇隨筆主要介紹 java 中 synchronized 關鍵字常用法,主要有以下四個方面:
1、實例方法同步
2、靜態方法同步
3、實例方法中同步塊
4、靜態方法中同步塊
我覺得在學習synchronized關鍵字之前,我們首先需要知道以下一點:Java 中每個實例對象對應一把鎖且每個實例對象只有一把鎖,synchronized 關鍵字是通過對相應的實例對象加鎖來實現同步功能的。
一、實例方法中使用 synchronized 加鎖
實例方法中默認被加鎖的對象是調用此方法的實例對象。
1 class ImmutableValue { 2 public synchronized void comeIn() throws InterruptedException{ 3 System.out.println(Thread.currentThread().getName() + ": start"); 4 Thread.sleep(5000); 5 System.out.println(Thread.currentThread().getName() + ": finish"); 6 } 7 public void synchronized comeInIn() throws InterruptedException { 8 System.out.println(Thread.currentThread().getName() + ": start"); 9 Thread.sleep(5000); 10 System.out.println(Thread.currentThread().getName() + ": finish"); 11 } 12 } 13 public class TestImmutableValue { 14 public static void main(String[] args) { 15 ImmutableValue im = new ImmutableValue(); 16 Thread t1 = new Thread(new Runnable() { 17 18 @Override 19 public void run() { 20 // TODO Auto-generated method stub 21 try { 22 im.comeIn(); 23 } catch (InterruptedException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } 27 } 28 29 }, "t1"); 30 Thread t2 = new Thread(new Runnable() { 31 32 @Override 33 public void run() { 34 // TODO Auto-generated method stub 35 try { 36 im.comeInIn(); 37 } catch (InterruptedException e) { 38 // TODO Auto-generated catch block 39 e.printStackTrace(); 40 } 41 } 42 43 }, "t2"); 44 t1.start(); 45 t2.start(); 46 } 47 }
在上面的代碼中創建了兩個線程並分別命名為 t1, t2。調用了同一個對象 im 的兩個同步方法 comeIn 和 comeInIn, 執行結果如下:
在 t1 線程開始執行後,即使 t1 線程睡眠了5s,線程 t2 中的 comeInIn 方法仍然沒有得到執行。這是因為 t1 線程先執行的 comeIn 方法,持有了對象 im 的鎖,且 comeIn 方法並沒有執行完,對象 im 的鎖沒有被釋放,所以 comeInIn 方法無法對對象 im 加鎖,就無法繼續執行,只能等到 t1 線程中的 comeIn 方法執行完畢,釋放對象 im 的鎖,comeInIn 方法才能繼續執行。
但是如果 t1 線程調用的是對象 im 的 comeIn 方法,而 t2 線程調用的是我們聲明的另外一個 ImmutableValue 對象 im2 對象的 comeInIn 方法,則這兩個方法的執行是互不影響的。因為 t1 線程的 comeIn 方法要獲得 im 對象的鎖,而 t2 線程要獲得的是 im2 對象的鎖,兩個鎖並不是同一個鎖(Java中每個實例對象都有且只有一個鎖),所以這兩個方法執行互不影響。
二、靜態方法中使用 synchronized 加鎖
靜態方法中默認被加鎖的對象是此靜態方法所在類的 class 對象。
1 class staticMethodSynchronized { 2 public static synchronized void method1() throws InterruptedException { 3 System.out.println(Thread.currentThread().getName() + ": start"); 4 Thread.sleep(5000); 5 System.out.println(Thread.currentThread().getName() + ": finish"); 6 } 7 public static synchronized void method2() throws InterruptedException { 8 System.out.println(Thread.currentThread().getName() + ": start"); 9 Thread.sleep(5000); 10 System.out.println(Thread.currentThread().getName() + ": finish"); 11 } 12 } 13 public class TestStaticClassSynchronized { 14 public static void main(String[] args) { 15 Thread t1 = new Thread(new Runnable() { 16 17 @Override 18 public void run() { 19 // TODO Auto-generated method stub 20 try { 21 staticMethodSynchronized.method1(); 22 } catch (InterruptedException e) { 23 // TODO Auto-generated catch block 24 e.printStackTrace(); 25 } 26 } 27 28 }, "t1"); 29 Thread t2 = new Thread(new Runnable() { 30 31 @Override 32 public void run() { 33 // TODO Auto-generated method stub 34 try { 35 staticMethodSynchronized.method2(); 36 } catch (InterruptedException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 } 41 42 }, "t2"); 43 t1.start(); 44 t2.start(); 45 } 46 }
在上述代碼中創建了兩個線程並命名為 t1,t2。 t1,t2 線程調用了 staticMethodSynchronized 類的兩個靜態同步方法 method1 和 method2。執行結果如下:
在 t1 線程開始執行後,即使 t1 線程睡眠了5s,線程 t2 中的 method2 方法仍然沒有得到執行。這是因為 t1 線程先執行的 method1 方法,持有了staticMethodSynchronized 類對象的鎖,且 method1 方法並沒有執行完,staticMethodSynchronized 類對象的鎖沒有被釋放,所以 comeInIn 方法無法對staticMethodSynchronized 類對象加鎖,就無法繼續執行,只能等到 t1 線程中的 method1 方法執行完畢,釋放 staticMethodSynchronized 類對象的鎖,method2 方法才能繼續執行。
三、實例方法中使用 synchronized 關鍵字制造同步塊
同步塊中默認被加鎖的對象是此同步塊括號聲明中包含的對象。
1 class ImmutableValue { 2 public synchronized void comeIn() throws InterruptedException{ 3 System.out.println(Thread.currentThread().getName() + ": start"); 4 Thread.sleep(5000); 5 System.out.println(Thread.currentThread().getName() + ": finish"); 6 } 7 public void comeInIn() throws InterruptedException { 8 System.out.println(Thread.currentThread().getName() + ": start"); 9 synchronized(this) { 10 11 } 12 System.out.println(Thread.currentThread().getName() + ": finish"); 13 } 14 } 15 public class TestImmutableValue { 16 public static void main(String[] args) { 17 ImmutableValue im = new ImmutableValue(); 18 Thread t1 = new Thread(new Runnable() { 19 20 @Override 21 public void run() { 22 // TODO Auto-generated method stub 23 try { 24 im.comeIn(); 25 } catch (InterruptedException e) { 26 // TODO Auto-generated catch block 27 e.printStackTrace(); 28 } 29 } 30 31 }, "t1"); 32 Thread t2 = new Thread(new Runnable() { 33 34 @Override 35 public void run() { 36 // TODO Auto-generated method stub 37 try { 38 im.comeInIn(); 39 } catch (InterruptedException e) { 40 // TODO Auto-generated catch block 41 e.printStackTrace(); 42 } 43 } 44 45 }, "t2"); 46 t1.start(); 47 t2.start(); 48 } 49 }
由以上代碼可以看到: 在 comeInIn 方法中,運用 synchronized(this) 制造同步塊,要執行同步塊內的代碼,就必須獲得 this 對象的鎖(調用 comeInIn 方法的對象)。
執行結果可能為:
由此執行結果可見:t1 線程先執行了 comeIn 方法,獲得了對象 im 的鎖,之後由於 t1 線程進入睡眠狀態,t2 線程得到運行,開始執行 comeInIn 方法,當執行到同步代碼塊時發現對象 im 已被加鎖,無法繼續執行。t1 線程睡眠結束之後繼續執行,結束後釋放對象 im 的鎖,t2 線程才能繼續執行。
四、靜態方法中使用 synchronized 關鍵字制造同步塊
同步塊中默認被加鎖的對象是此同步塊括號聲明中包含的對象。
1 class staticMethodSynchronized { 2 private static final Object OBJ = new Object(); 3 public static void method1() throws InterruptedException { 4 System.out.println(Thread.currentThread().getName() + ": start"); 5 synchronized(OBJ) { 6 System.out.println(Thread.currentThread().getName() + ": 獲得鎖"); 7 System.out.println(Thread.currentThread().getName() + ": 釋放鎖"); 8 } 9 System.out.println(Thread.currentThread().getName() + ": finish"); 10 } 11 public static void method2() throws InterruptedException { 12 System.out.println(Thread.currentThread().getName() + ": start"); 13 synchronized(OBJ) { 14 System.out.println(Thread.currentThread().getName() + ": 獲得鎖"); 15 System.out.println(Thread.currentThread().getName() + ": 釋放鎖"); 16 } 17 System.out.println(Thread.currentThread().getName() + ": finish"); 18 } 19 } 20 public class TestStaticClassSynchronized { 21 public static void main(String[] args) { 22 Thread t1 = new Thread(new Runnable() { 23 24 @Override 25 public void run() { 26 // TODO Auto-generated method stub 27 try { 28 staticMethodSynchronized.method1(); 29 } catch (InterruptedException e) { 30 // TODO Auto-generated catch block 31 e.printStackTrace(); 32 } 33 } 34 35 }, "t1"); 36 Thread t2 = new Thread(new Runnable() { 37 38 @Override 39 public void run() { 40 // TODO Auto-generated method stub 41 try { 42 staticMethodSynchronized.method2(); 43 } catch (InterruptedException e) { 44 // TODO Auto-generated catch block 45 e.printStackTrace(); 46 } 47 } 48 49 }, "t2"); 50 t1.start(); 51 t2.start(); 52 } 53 }
在上述代碼中,兩個靜態方法中的同步塊都要獲得對象 OBJ 的鎖才能繼續向下執行,執行結果可能如下:
若 t1 線程先獲得鎖,則必須等到 t1 釋放鎖之後,t2 線程中同步代碼塊及其之後的代碼才能繼續執行,t2 線程先獲得鎖,t1 線程同理。
總之,我認為我們只需抓住一點:Java 中每個實例對象對應一把鎖且每個實例對象只有一把鎖,synchronized 關鍵字是通過對相應的實例對象加鎖來實現同步功能的(靜態方法為對相應的 class 對象加鎖)。在執行 synchronized 方法或 synchronized 同步塊之前,我們只需判斷其需要獲得的對象的鎖是否可獲得,就可判斷此方法或同步塊是否可得到執行。