Java程式中的多執行緒(2)(轉)

ba發表於2007-08-15
Java程式中的多執行緒(2)(轉)[@more@]synchronized 關鍵字

  到目前為止,我們看到的示例都只是以非常簡單的方式來利用執行緒。只有最小的資料流,而且不會出現兩個執行緒訪問同一個物件的情況。但是,在大多數有用的程式中,執行緒之間通常有資訊流。試考慮一個金融應用程式,它有一個 Account 物件,如下例中所示:

  一個銀行中的多項活動

public class Account {
String holderName;
float amount;
public Account(String name, float amt) {
holderName = name;
amount = amt;
}
public void deposit(float amt) {
amount += amt;
}
public void withdraw(float amt) {
amount -= amt;
}
public float checkBalance() {
return amount;
}
}


  在此程式碼樣例中潛伏著一個錯誤。如果此類用於單執行緒應用程式,不會有任何問題。但是,在多執行緒應用程式的情況中,不同的執行緒就有可能同時訪問同一個 Account 物件,比如說一個聯合帳戶的所有者在不同的 ATM 上同時進行訪問。在這種情況下,存入和支出就可能以這樣的方式發生:一個事務被另一個事務覆蓋。這種情況將是災難性的。但是,Java 程式語言提供了一種簡單的機制來防止發生這種覆蓋。每個物件在執行時都有一個關聯的鎖。這個鎖可透過為方法新增關鍵字   synchronized 來獲得。這樣,修訂過的 Account 物件(如下所示)將不會遭受像資料損壞這樣的錯誤:

  對一個銀行中的多項活動進行同步處理

public class Account {
String holderName;
float amount;
public Account(String name, float amt) {
holderName = name;
amount = amt;
}
public synchronized void deposit(float amt) {
amount += amt;
}
public synchronized void withdraw(float amt) {
amount -= amt;
}
public float checkBalance() {
return amount;
}
}


  deposit() 和 withdraw() 函式都需要這個鎖來進行操作,所以當一個函式執行時,另一個函式就被阻塞。請注意,checkBalance() 未作更改,它嚴格是一個讀函式。因為 checkBalance() 未作同步處理,所以任何其他方法都不會阻塞它,它也不會阻塞任何其他方法,不管那些方法是否進行了同步處理。



Java 程式語言中的高階多執行緒支援

  執行緒組

  執行緒是被個別建立的,但可以將它們歸類到執行緒組中,以便於除錯和監視。只能在建立執行緒的同時將它與一個執行緒組相關聯。在使用大量執行緒的程式中,使用執行緒組組織執行緒可能很有幫助。可以將它們看作是計算機上的目錄和檔案結構。

  執行緒間發信
  當執行緒在繼續執行前需要等待一個條件時,僅有 synchronized 關鍵字是不夠的。雖然 synchronized 關鍵字阻止併發更新一個物件,但它沒有實現執行緒間發信。Object 類為此提供了三個函式:wait()、notify() 和 notifyAll()。以全球氣候預測程式為例。這些程式透過將地球分為許多單元,在每個迴圈中,每個單元的計算都是隔離進行的,直到這些值趨於穩定,然後相鄰單元之間就會交換一些資料。所以,從本質上講,在每個迴圈中各個執行緒都必須等待所有執行緒完成各自的任務以後才能進入下一個迴圈。這個模型稱為遮蔽同步,下例說明了這個模型:

  遮蔽同步 public class BSync {
int totalThreads;
int currentThreads;
public BSync(int x) {
totalThreads = x;
currentThreads = 0;
}
public synchronized void waitForAll() {
currentThreads++;
if(currentThreads < totalThreads) {
try {
wait();
} catch (Exception e) {}
}
else {
currentThreads = 0;
notifyAll();
}
}
}


  當對一個執行緒呼叫 wait() 時,該執行緒就被有效阻塞,只到另一個執行緒對同一個物件呼叫 notify() 或 notifyAll() 為止。因此,在前一個示例中,不同的執行緒在完成它們的工作以後將呼叫 waitForAll() 函式,最後一個執行緒將觸發 notifyAll() 函式,該函式將釋放所有的執行緒。第三個函式 notify() 只通知一個正在等待的執行緒,當對每次只能由一個執行緒使用的資源進行訪問限制時,這個函式很有用。但是,不可能預知哪個執行緒會獲得這個通知,因為這取決於 Java 虛擬機器 (JVM) 排程演算法。

  將 CPU 讓給另一個執行緒

  當執行緒放棄某個稀有的資源(如資料庫連線或網路埠)時,它可能呼叫 yield() 函式臨時降低自己的優先順序,以便某個其他執行緒能夠執行。

  守護執行緒

  有兩類執行緒:使用者執行緒和守護執行緒。使用者執行緒是那些完成有用工作的執行緒。守護執行緒是那些僅提供輔助功能的執行緒。Thread 類提供了 setDaemon() 函式。Java 程式將執行到所有使用者執行緒終止,然後它將破壞所有的守護執行緒。在 Java 虛擬機器 (JVM) 中,即使在 main 結束以後,如果另一個使用者執行緒仍在執行,則程式仍然可以繼續執行。

  避免不提倡使用的方法

  不提倡使用的方法是為支援向後相容性而保留的那些方法,它們在以後的版本中可能出現,也可能不出現。Java 多執行緒支援在版本 1.1 和版本 1.2 中做了重大修訂,stop()、suspend() 和 resume() 函式已不提倡使用。這些函式在 JVM 中可能引入微妙的錯誤。雖然函式名可能聽起來很誘人,但請抵制誘惑不要使用它們。

  除錯執行緒化的程式

  線上程化的程式中,可能發生的某些常見而討厭的情況是死鎖、活鎖、記憶體損壞和資源耗盡。

  死鎖

  死鎖可能是多執行緒程式最常見的問題。當一個執行緒需要一個資源而另一個執行緒持有該資源的鎖時,就會發生死鎖。這種情況通常很難檢測。但是,解決方案卻相當好:在所有的執行緒中按相同的次序獲取所有資源鎖。例如,如果有四個資源 ?A、B、C 和 D ? 並且一個執行緒可能要獲取四個資源中任何一個資源的鎖,則請確保在獲取對 B 的鎖之前首先獲取對 A 的鎖,依此類推。如果“執行緒 1”希望獲取對 B 和 C 的鎖,而“執行緒 2”獲取了 A、C 和 D 的鎖,則這一技術可能導致阻塞,但它永遠不會在這四個鎖上造成死鎖。

  活鎖

  當一個執行緒忙於接受新任務以致它永遠沒有機會完成任何任務時,就會發生活鎖。這個執行緒最終將超出緩衝區並導致程式崩潰。試想一個秘書需要錄入一封信,但她一直在忙於接電話,所以這封信永遠不會被錄入。

  記憶體損壞

  如果明智地使用 synchronized 關鍵字,則完全可以避免記憶體錯誤這種氣死人的問題。

  資源耗盡

  某些系統資源是有限的,如檔案描述符。多執行緒程式可能耗盡資源,因為每個執行緒都可能希望有一個這樣的資源。如果執行緒數相當大,或者某個資源的侯選執行緒數遠遠超過了可用的資源數,則最好使用資源池。一個最好的示例是資料庫連線池。只要執行緒需要使用一個資料庫連線,它就從池中取出一個,使用以後再將它返回池中。資源池也稱為資源庫。

  除錯大量的執行緒

  有時一個程式因為有大量的執行緒在執行而極難除錯。在這種情況下,下面的這個類可能會派上用場: public class Probe extends Thread {
public Probe() {}
public void run() {
while(true) {
Thread[] x = new Thread[100];
Thread.enumerate(x);
for(int i=0; i<100; i++) {
Thread t = x;
if(t == null)
break;
else
System.out.println(t.getName() + " " + t.getPriority()
+ " " + t.isAlive() + " " + t.isDaemon());
}
}
}
}


  限制執行緒優先順序和排程

  Java 執行緒模型涉及可以動態更改的執行緒優先順序。本質上,執行緒的優先順序是從 1 到 10 之間的一個數字,數字越大表明任務越緊急。JVM 標準首先呼叫優先順序較高的執行緒,然後才呼叫優先順序較低的執行緒。但是,該標準對具有相同優先順序的執行緒的處理是隨機的。如何處理這些執行緒取決於基層的作業系統策略。在某些情況下,優先順序相同的執行緒分時執行;在另一些情況下,執行緒將一直執行到結束。請記住,Java 支援 10 個優先順序,基層作業系統支援的優先順序可能要少得多,這樣會造成一些混亂。因此,只能將優先順序作為一種很粗略的工具使用。最後的控制可以透過明智地使用 yield() 函式來完成。通常情況下,請不要依靠執行緒優先順序來控制執行緒的狀態。

  小結

  本文說明了在 Java 程式中如何使用執行緒。像是否應該使用執行緒這樣的更重要的問題在很大程式上取決於手頭的應用程式。決定是否在應用程式中使用多執行緒的一種方法是,估計可以並行執行的程式碼量。並記住以下幾點:

  1、使用多執行緒不會增加 CPU 的能力。但是如果使用 JVM 的本地執行緒實現,則不同的執行緒可以在不同的處理器上同時執行(在多 CPU 的機器中),從而使多 CPU 機器得到充分利用。如果應用程式是計算密集型的,並受 CPU 功能的制約,則只有多 CPU 機器能夠從更多的執行緒中受益。 當應用程式必須等待緩慢的資源(如網路連線或資料庫連線)時,或者當應用程式是非互動式的時,多執行緒通常是有利的。

  2、基於 Internet 的軟體有必要是多執行緒的;否則,使用者將感覺應用程式反映遲鈍。例如,當開發要支援大量客戶機的伺服器時,多執行緒可以使程式設計較為容易。在這種情況下,每個執行緒可以為不同的客戶或客戶組服務,從而縮短了響應時間。

  3、某些程式設計師可能在 C 和其他語言中使用過執行緒,在那些語言中對執行緒沒有語言支援。這些程式設計師可能通常都被搞得對執行緒失去了信心。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-958519/,如需轉載,請註明出處,否則將追究法律責任。

相關文章