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

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

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

Java 同步關鍵字(synchronized)

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

有四種不同的同步塊:

  • 實例方法
  • 靜態方法
  • 實例方法中的同步塊
  • 靜態方法中的同步塊

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

實例方法同步

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

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

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

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

靜態方法同步

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

<code>public static synchronized void add(int value){
count += value;
}/<code>

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

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

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

實例方法中的同步塊

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

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

<code>public void add(int value){

synchronized(this){
this.count += value;
}
}/<code>

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

注意Java同步塊構造器用括號將對象括起來。在上例中,使用了“this”,即為調用add方法的實例本身。在同步構造器中用括號括起來的對象叫做監視器對象。上述代碼使用監視器對象同步,同步實例方法使用調用方法本身的實例作為監視器對象。

一次只有一個線程能夠在同步於同一個監視器對象的Java方法內執行。

下面兩個例子都同步他們所調用的實例對象上,因此他們在同步的執行效果上是等效的。

<code>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);
}
}
}/<code>

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

如果第二個同步塊不是同步在this實例對象上,那麼兩個方法可以被線程同時執行。

靜態方法中的同步塊

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

<code>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);
}
}
}/<code>

這兩個方法不允許同時被線程訪問。

如果第二個同步塊不是同步在MyClass.class這個對象上。那麼這兩個方法可以同時被線程訪問。

Java同步實例

在下面例子中,啟動了兩個線程,都調用Counter類同一個實例的add方法。因為同步在該方法所屬的實例上,所以同時只能有一個線程訪問該方法。

<code>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();
}
}/<code>

創建了兩個線程。他們的構造器引用同一個Counter實例。Counter.add方法是同步在實例上,是因為add方法是實例方法並且被標記上synchronized關鍵字。因此每次只允許一個線程調用該方法。另外一個線程必須要等到第一個線程退出add()方法時,才能繼續執行方法。

如果兩個線程引用了兩個不同的Counter實例,那麼他們可以同時調用add()方法。這些方法調用了不同的對象,因此這些方法也就同步在不同的對象上。這些方法調用將不會被阻塞。如下面這個例子所示:

<code>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();
}
}/<code>

注意這兩個線程,threadA和threadB,不再引用同一個counter實例。CounterA和counterB的add方法同步在他們所屬的對象上。調用counterA的add方法將不會阻塞調用counterB的add方法。



分享到:


相關文章: