Java線程同步:synchronized鎖住的是代碼還是對象

在Java中,synchronized關鍵字是用來控制線程同步的,就是在多線程的環境下,控制synchronized代碼段不被多個線程同時執行。synchronized既可以加在一段代碼上,也可以加在方法上。

關鍵是,不要認爲給方法或者代碼段加上synchronized就萬事大吉,看下面一段代碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class Sync {  
  2.   
  3.     public synchronized void test() {  
  4.         System.out.println("test開始..");  
  5.         try {  
  6.             Thread.sleep(1000);  
  7.         } catch (InterruptedException e) {  
  8.             e.printStackTrace();  
  9.         }  
  10.         System.out.println("test結束..");  
  11.     }  
  12. }  
  13.   
  14. class MyThread extends Thread {  
  15.   
  16.     public void run() {  
  17.         Sync sync = new Sync();  
  18.         sync.test();  
  19.     }  
  20. }  
  21.   
  22. public class Main {  
  23.   
  24.     public static void main(String[] args) {  
  25.         for (int i = 0; i < 3; i++) {  
  26.             Thread thread = new MyThread();  
  27.             thread.start();  
  28.         }  
  29.     }  
  30. }  

運行結果:
test開始..
test開始..
test開始..
test結束..
test結束..
test結束..

可以看出來,上面的程序起了三個線程,同時運行Sync類中的test()方法,雖然test()方法加上了synchronized,但是還是同時運行起來,貌似synchronized沒起作用。 

將test()方法上的synchronized去掉,在方法內部加上synchronized(this):

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void test() {  
  2.     synchronized(this){  
  3.         System.out.println("test開始..");  
  4.         try {  
  5.             Thread.sleep(1000);  
  6.         } catch (InterruptedException e) {  
  7.             e.printStackTrace();  
  8.         }  
  9.         System.out.println("test結束..");  
  10.     }  
  11. }  

運行結果:
test開始..
test開始..
test開始..
test結束..
test結束..
test結束..

一切還是這麼平靜,沒有看到synchronized起到作用。 

實際上,synchronized(this)以及非static的synchronized方法(至於static synchronized方法請往下看),只能防止多個線程同時執行同一個對象的同步代碼段。

回到本文的題目上:synchronized鎖住的是代碼還是對象。答案是:synchronized鎖住的是括號裏的對象,而不是代碼。對於非static的synchronized方法,鎖的就是對象本身也就是this。

當synchronized鎖住一個對象後,別的線程如果也想拿到這個對象的鎖,就必須等待這個線程執行完成釋放鎖,才能再次給對象加鎖,這樣才達到線程同步的目的。即使兩個不同的代碼段,都要鎖同一個對象,那麼這兩個代碼段也不能在多線程環境下同時運行。

所以我們在用synchronized關鍵字的時候,能縮小代碼段的範圍就儘量縮小,能在代碼段上加同步就不要再整個方法上加同步。這叫減小鎖的粒度,使代碼更大程度的併發。原因是基於以上的思想,鎖的代碼段太長了,別的線程是不是要等很久,等的花兒都謝了。當然這段是題外話,與本文核心思想並無太大關聯。

再看上面的代碼,每個線程中都new了一個Sync類的對象,也就是產生了三個Sync對象,由於不是同一個對象,所以可以多線程同時運行synchronized方法或代碼段。

爲了驗證上述的觀點,修改一下代碼,讓三個線程使用同一個Sync的對象。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class MyThread extends Thread {  
  2.   
  3.     private Sync sync;  
  4.   
  5.     public MyThread(Sync sync) {  
  6.         this.sync = sync;  
  7.     }  
  8.   
  9.     public void run() {  
  10.         sync.test();  
  11.     }  
  12. }  
  13.   
  14. public class Main {  
  15.   
  16.     public static void main(String[] args) {  
  17.         Sync sync = new Sync();  
  18.         for (int i = 0; i < 3; i++) {  
  19.             Thread thread = new MyThread(sync);  
  20.             thread.start();  
  21.         }  
  22.     }  
  23. }  

運行結果:
test開始..
test結束..
test開始..
test結束..
test開始..
test結束..

可以看到,此時的synchronized就起了作用。 

那麼,如果真的想鎖住這段代碼,要怎麼做?也就是,如果還是最開始的那段代碼,每個線程new一個Sync對象,怎麼才能讓test方法不會被多線程執行。 

解決也很簡單,只要鎖住同一個對象不就行了。例如,synchronized後的括號中鎖同一個固定對象,這樣就行了。這樣是沒問題,但是,比較多的做法是讓synchronized鎖這個類對應的Class對象。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class Sync {  
  2.   
  3.     public void test() {  
  4.         synchronized (Sync.class) {  
  5.             System.out.println("test開始..");  
  6.             try {  
  7.                 Thread.sleep(1000);  
  8.             } catch (InterruptedException e) {  
  9.                 e.printStackTrace();  
  10.             }  
  11.             System.out.println("test結束..");  
  12.         }  
  13.     }  
  14. }  
  15.   
  16. class MyThread extends Thread {  
  17.   
  18.     public void run() {  
  19.         Sync sync = new Sync();  
  20.         sync.test();  
  21.     }  
  22. }  
  23.   
  24. public class Main {  
  25.   
  26.     public static void main(String[] args) {  
  27.         for (int i = 0; i < 3; i++) {  
  28.             Thread thread = new MyThread();  
  29.             thread.start();  
  30.         }  
  31.     }  
  32. }  

運行結果:
test開始..
test結束..
test開始..
test結束..
test開始..
test結束..

上面代碼用synchronized(Sync.class)實現了全局鎖的效果

最後說說static synchronized方法,static方法可以直接類名加方法名調用,方法中無法使用this,所以它鎖的不是this,而是類的Class對象,所以,static synchronized方法也相當於全局鎖,相當於鎖住了代碼段。

發佈了55 篇原創文章 · 獲贊 132 · 訪問量 84萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章