Java同步塊(synchronized block)使用詳解

李同傑發表於2016-07-28

Java 同步塊(synchronized block)用來標記方法或者程式碼塊是同步的。Java同步塊用來避免競爭。本文介紹以下內容:

  • Java同步關鍵字(synchronzied)
  • 例項方法同步
  • 靜態方法同步
  • 例項方法中同步塊
  • 靜態方法中同步塊
  • Java同步示例

Java 同步關鍵字(synchronized)

Java中的同步塊用synchronized標記。同步塊在Java中是同步在某個物件上。所有同步在一個物件上的同步塊在同時只能被一個執行緒進入並執行操作。所有其他等待進入該同步塊的執行緒將被阻塞,直到執行該同步塊中的執行緒退出。

有四種不同的同步塊:

  1. 例項方法
  2. 靜態方法
  3. 例項方法中的同步塊
  4. 靜態方法中的同步塊

上述同步塊都同步在不同物件上。實際需要那種同步塊視具體情況而定。

例項方法同步

下面是一個同步的例項方法:

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

注意在方法宣告中同步(synchronized )關鍵字。這告訴Java該方法是同步的。

Java例項方法同步是同步在擁有該方法的物件上。這樣,每個例項其方法同步都同步在不同的物件上,即該方法所屬的例項。只有一個執行緒能夠在例項方法同步塊中執行。如果有多個例項存在,那麼一個執行緒一次可以在一個例項同步塊中執行操作。一個例項一個執行緒。

靜態方法同步

靜態方法同步和例項方法同步方法一樣,也使用synchronized 關鍵字。Java靜態方法同步如下示例:

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

同樣,這裡synchronized 關鍵字告訴Java這個方法是同步的。

靜態方法的同步是指同步在該方法所在的類物件上。因為在Java虛擬機器中一個類只能對應一個類物件,所以同時只允許一個執行緒執行同一個類中的靜態同步方法。

對於不同類中的靜態同步方法,一個執行緒可以執行每個類中的靜態同步方法而無需等待。不管類中的那個靜態同步方法被呼叫,一個類只能由一個執行緒同時執行。

例項方法中的同步塊

有時你不需要同步整個方法,而是同步方法中的一部分。Java可以對方法的一部分進行同步。

在非同步的Java方法中的同步塊的例子如下所示:

public void add(int value){

    synchronized(this){
       this.count += value;
    }
  }

示例使用Java同步塊構造器來標記一塊程式碼是同步的。該程式碼在執行時和同步方法一樣。

注意Java同步塊構造器用括號將物件括起來。在上例中,使用了“this”,即為呼叫add方法的例項本身。在同步構造器中用括號括起來的物件叫做監視器物件。上述程式碼使用監視器物件同步,同步例項方法使用呼叫方法本身的例項作為監視器物件。

一次只有一個執行緒能夠在同步於同一個監視器物件的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);
      }
   }
 }

在上例中,每次只有一個執行緒能夠在兩個同步塊中任意一個方法內執行。

如果第二個同步塊不是同步在this例項物件上,那麼兩個方法可以被執行緒同時執行。

靜態方法中的同步塊

和上面類似,下面是兩個靜態方法同步的例子。這些方法同步在該方法所屬的類物件上。

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同步例項

在下面例子中,啟動了兩個執行緒,都呼叫Counter類同一個例項的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()方法時,才能繼續執行方法。

如果兩個執行緒引用了兩個不同的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,不再引用同一個counter例項。CounterA和counterB的add方法同步在他們所屬的物件上。呼叫counterA的add方法將不會阻塞呼叫counterB的add方法。

相關文章