1. 程式人生 > >Java:synchronized關鍵字

Java:synchronized關鍵字

原帖收藏於IT老兵部落格

前言

這裡翻譯一篇對Java的synchronized關鍵字的講解文章,這個關鍵字用於解決Java世界裡面競爭條件時的訪問衝突問題。

正文

Java synchronized塊將方法或程式碼塊標記為已同步。 Java synchronized塊可用於避免競爭條件。

Java同步關鍵字

Java中的同步塊使用synchronized關鍵字標記。Java中的同步塊在某個物件上同步。在同一物件上的所有同步塊在同一時刻只能被一個執行緒執行。嘗試進入同步塊的所有其他執行緒將被阻塞,直到同步塊內的執行緒退出塊。

synchronized關鍵字可用於標記四種不同型別的塊:

  1. 例項方法
  2. 靜態方法
  3. 例項方法中的程式碼塊
  4. 靜態方法中的程式碼塊

這些塊在不同物件上同步,您需要哪種型別的同步塊取決於具體情況。

同步例項方法

這是一個同步的例項方法:

  public synchronized void add(int value){
      this.count + = value;
  }

請注意在方法宣告中使用synchronized關鍵字,這告訴Java該方法是同步的。

Java中的同步例項方法在擁有該方法的例項(物件)上同步。因此,每個例項的同步方法在不同的物件上同步:擁有它的例項。在一個同步例項方法中,只有一個執行緒可以執行。如果存在多個例項,則一次只能有一個執行緒可以在每個例項的同步例項方法內執行。每個例項一個執行緒。

同步靜態方法

靜態方法被標記為synchronized,就像使用synchronized關鍵字的例項方法一樣。這是一個Java synchronized靜態方法示例:

  public static synchronized void add(int value){
      count + = value;
  }

此處,synchronized關鍵字告訴Java該方法已同步。

同步靜態方法在同步靜態方法所屬的類的類物件上同步。由於每個類在Java VM中只存在一個類物件,因此在同一個類中的靜態同步方法中只能執行一個執行緒。

如果靜態同步方法位於不同的類中,則一個執行緒可以在每個類的靜態同步方法內執行。每個類一個執行緒,無論它呼叫哪個靜態同步方法。

例項方法中的同步塊

您不需要同步整個方法,有時只需要同步方法的一部分。方法中的Java同步塊使這成為可能。

以下是非同步Java方法中的同步Java程式碼塊:

  public void add(int value){
    synchronized(this){
       this.count + = value;
    }
  }

此示例使用Java synchronized塊構造將程式碼塊標記為已同步。此程式碼現在將像執行同步方法一樣執行。

請注意Java synchronized塊構造如何在括號中獲取物件。在示例中使用“this”,這是呼叫add方法的例項。由synchronized構造在括號中獲取的物件稱為監視器物件。據說程式碼在監視器物件上同步。同步例項方法使用它所屬的物件作為監視物件。

只有一個執行緒可以在同一監視器物件上同步的Java程式碼塊內執行。

以下兩個示例在呼叫它們的例項上同步。因此,它們在同步方面是等效的:

  public class MyClass {
    public synchronized void log1(String msg1,String msg2){
       log.writeln(MSG1);
       log.writeln(MSG2);
    }
    
    public void log2(String msg1,String msg2){
       synchronized(this){
          log.writeln(MSG1);
          log.writeln(MSG2);
       }
    }
  }

因此,在該示例中,只有單個執行緒可以在兩個同步塊中的任一個內執行。

如果第二個同步塊在與此不同的物件上同步,則一次一個執行緒就能夠在每個方法內執行。

靜態方法中的同步塊

以下是與靜態方法相同的兩個示例。這些方法在方法所屬的類的類物件上同步:

  public class MyClass {

    public static synchronized void log1(String msg1,String msg2){
       log.writeln(MSG1);
       log.writeln(MSG2);
    }

  
    public static void log2(String msg1,String msg2){
       synchronized(MyClass.class){
          log.writeln(MSG1);
          log.writeln(MSG2);
       }
    }
  }

只有一個執行緒可以同時在這兩個方法中的任何一個內執行。

如果第二個同步塊在與MyClass.class不同的物件上同步,那麼一個執行緒可以同時在每個方法內執行。

Java同步例子

下面是一個示例,它啟動2個執行緒並讓它們在同一個Counter例項上呼叫add方法。 一次只有一個執行緒能夠在同一個例項上呼叫add方法,因為該方法在它所屬的例項上是同步的。

  public class Counter {
     
     long count = 0;
    
     public synchronized void add(long value) {
       this.count += value;
     }
  }
  public class CounterThread extends Thread {

     protected Counter counter = null;

     public CounterThread(Counter counter) {
        this.counter = counter;
     }

     public void run() {
         for (int i = 0;  i < 10;  i++) {
           counter.add(i);
        }
     }
  }
  public class Example {

    public static void main(String[] args) {
      Counter counter = new Counter();
      Thread  threadA = new CounterThread(counter);
      Thread  threadB = new CounterThread(counter);

      threadA.start();
      threadB.start(); 
    }
  }

建立了兩個執行緒。 相同的Counter例項在其建構函式中傳遞給它們。 Counter.add()方法在例項上同步,因為add方法是一個例項方法,並標記為synchronized。 因此,只有一個執行緒可以一次呼叫add()方法。 另一個執行緒將等到第一個執行緒離開add()方法,然後才能執行方法本身。

如果兩個執行緒引用了兩個單獨的Counter例項,那麼同時呼叫add()方法就沒有問題。 呼叫將是對不同的物件,因此呼叫的方法也將在不同的物件(擁有該方法的物件)上同步。 因此呼叫不會阻止。 這是如何看起來:

  public class Example {

    public static void main(String[] args) {
      Counter counterA = new Counter();
      Counter counterB = new Counter();
      Thread  threadA = new CounterThread(counterA);
      Thread  threadB = new CounterThread(counterB);

      threadA.start();
      threadB.start(); 
    }
  }

注意兩個執行緒threadA和threadB如何不再引用相同的計數器例項。 counterA和counterB的add方法在它們的兩個擁有例項上同步。 因此,在counterA上呼叫add()將不會阻止對counterB的add()呼叫。

Java併發實用程式

同步機制是Java的第一種機制,用於同步對多個執行緒共享的物件的訪問。但同步機制並不是很先進。 這就是為什麼Java 5獲得了一整套concurrency utility classes來幫助開發人員實現比同步所獲得的更細粒度的併發控制。

總結

這篇文章對synchronized關鍵字進行了詳細的講解,個人感覺講解的不錯,翻譯出來,做個記錄。

參考

http://tutorials.jenkov.com/java-concurrency/synchronized.html