Java多線程編程技術中的進階應用程式

來源:互聯網
上載者:User

線程組

線程是被個別建立的,但可以將它們歸類到線程組中,以便於調試和監視。只能在建立線程的同時將它與一個線程組相關聯。在使用大量線程的程式中,使用線程組組織線程可能很有協助。可以將它們看作是電腦上的目錄和檔案結構。

線程間發信

當線程在繼續執行前需要等待一個條件時,僅有 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[i];
    if(t == null)
     break;
    else
     System.out.println(
t.getName() + "/t" + t.getPriority()+ "/t" + t.isAlive() + "/t" +
t.isDaemon());
   }
  }
 }
}

限制線程優先順序和調度

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

小結

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

使用多線程不會增加 CPU 的能力。但是如果使用 JVM 的本地線程實現,則不同的線程可以在不同的處理器上同時運行(在多 CPU 的機器中),從而使多 CPU 機器得到充分利用。

如果應用程式是計算密集型的,並受 CPU 功能的制約,則只有多 CPU 機器能夠從更多的線程中受益。

當應用程式必須等待緩慢的資源(如網路連接或資料庫連接)時,或者當應用程式是非互動時,多線程通常是有利的。

基於 Internet 的軟體有必要是多線程的;否則,使用者將感覺應用程式反映遲鈍。例如,當開發要支援大量客戶機的伺服器時,多線程可以使編程較為容易。在這種情況下,每個線 程可以為不同的客戶或客戶組服務,從而縮短了回應時間。 某些程式員可能在 C 和其他語言中使用過線程,在那些語言中對線程沒有語言支援。這些程式員可能通常都被搞得對線程失去了信心。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.