天天看點

Java 并發/多線程教程(十二)-JAVA同步塊

本系列譯自jakob jenkov的Java并發多線程教程,個人覺得很有收獲。由于個人水準有限,不對之處還望矯正!

一個Java同步塊标記一個方法或一個代碼塊作為同步。可以使用Java同步塊來避免競态條件。

      在Java中同步的塊被标記為Synchronized關鍵字。Java中的同步塊在某些對象上是同步的。在同一對象上同步的所有同步塊隻能在同一時間内執行一個線程。所有試圖進入同步塊的其他線程都被阻塞,直到同步塊中的線程退出該塊.

Synchronized關鍵字可以用來标記四種不同類型的塊:

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

是以,隻有一個線程可以在這個示例中的兩個同步塊中的任何一個中執行。如果第二個同步塊在一個不同的對象上被同步,那麼一次一個線程就能夠在每個方法中執行。

下面是靜态方法的兩個例子。這些方法在類的類對象上是同步的:方法屬于:

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

在這兩種方法中,隻有一個線程可以同時執行。如果第二個同步塊在一個不同的對象上被同步,而不是MyClass類,然後一個線程可以同時在每個方法内執行。

這裡有一個例子,它啟動了兩個線程,并讓它們都在同一個計數器執行個體上調用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();                 ThreadthreadA = new CounterThread(counter);                 ThreadthreadB = new CounterThread(counter);                  threadA.start();                  threadB.start();         }

建立兩個線程。相同的計數器執行個體在它們的構造函數中傳遞給它們。add()方法在執行個體上是同步的,因為add方法是一個執行個體方法,并且标記為synchronized。是以,隻有一個線程可以一次調用add()方法。另一個線程将等待第一個線程離開add()方法,然後才能執行該方法本身。

如果這兩個線程引用了兩個單獨的計數器執行個體,那麼就不會同時調用add()方法了。調用的對象應該是不同的對象,是以調用的方法也會在不同的對象上同步(擁有該方法的對象)。是以,通話不會阻塞。這就是它的樣子:

          public static void main(String[] args){                  Counter counterA = new Counter();                  Counter counterB = new Counter();                  ThreadthreadA = new CounterThread(counterA);                  ThreadthreadB = new CounterThread(counterB);

注意兩個線程,threadA和threadB,不再引用相同的計數器執行個體。countA和countB的添加方法在它們的兩個擁有執行個體上是同步的。調用add()在countA将不會阻塞在countB對add()的調用。

同步機制是Java的第一種用于同步對多個線程共享的對象的通路機制。盡管如此,同步機制并不十分先進。這就是為什麼Java 5得了一套完整的并發工具類,以幫助開發人員實作更細粒度的并發控制,而不是同步的。