本系列譯自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得了一套完整的并發工具類,以幫助開發人員實作更細粒度的并發控制,而不是同步的。