本教程研究了線程的基礎知識 ― 線程是什麼、線程為什麼有用以及怎麼開始編寫使用線程的簡單程式。 (推薦課程:Java影片教學)
#什麼是執行緒?
幾乎每個作業系統都支援進程的概念-- 進程就是在某種程度上相互隔離的、獨立運行的程式。
執行緒化是允許多個活動共存於一個行程中的工具。大多數現代的作業系統都支援線程,而且線程的概念以各種形式已經存在了多年。 Java 是第一個在語言本身中明確地包含執行緒的主流程式語言,它沒有把執行緒化視為底層作業系統的工具。
有時候,執行緒也稱作輕量級進程。就像進程一樣,執行緒在程式中是獨立的、並發的執行路徑,每個執行緒都有它自己的堆疊、自己的程式計數器和自己的局部變數。但是,與分隔的進程相比,進程中的執行緒之間的隔離程度較小。它們共享記憶體、檔案句柄和其它每個進程應有的狀態。
進程可以支援多個線程,它們看似同時執行,但互相之間並不同步。一個進程中的多個執行緒共享相同的記憶體位址空間,這意味著它們可以存取相同的變數和對象,而且它們從同一堆中分配對象。儘管這讓執行緒之間共享資訊變得更容易,但您必須小心,確保它們不會妨礙同一進程中的其它執行緒。
Java 執行緒工具和 API 看似簡單。但是,編寫有效使用執行緒的複雜程式並不十分容易。因為有多個執行緒共存在相同的記憶體空間中並共享相同的變量,所以您必須小心,確保您的執行緒不會互相干擾。
每個 Java 程式都使用執行緒
每個 Java 程式都至少有一個執行緒 ― 主執行緒。當一個 Java 程式啟動時,JVM 會建立主線程,並在該線程中呼叫程式的 main() 方法。
JVM 還創建了其它線程,您通常都看不到它們 ― 例如,與垃圾收集、物件終止和其它 JVM 內務處理任務相關的線程。其它工具也創建線程,如 AWT(抽象視窗工具箱(Abstract Windowing Toolkit))或 Swing UI 工具箱、servlet 容器、應用程式伺服器和 RMI(遠端方法呼叫(Remote Method Invocation))。
為什麼要使用執行緒?
在 Java 程式中使用執行緒有許多原因。如果您使用 Swing、servlet、RMI 或 Enterprise JavaBeans(EJB)技術,您也許沒有意識到您已經在使用線程了。
使用執行緒的一些原因是它們可以幫助:
使UI 反應更快
#利用多處理器系統
簡化建模
#執行非同步或後台處理
回應更快的UI
#事件驅動的UI 工具箱(如AWT 和Swing)有一個事件線程,它處理UI 事件,如按鍵或滑鼠點擊。
AWT 和 Swing 程式把事件偵聽器與 UI 物件連接。當特定事件(如點擊了某個按鈕)發生時,這些偵聽器會被通知。事件偵聽器是在 AWT 事件線程中呼叫的。
如果事件偵聽器要執行持續很久的任務,如檢查一個大文檔中的拼寫,事件線程將忙於運行拼寫檢查器,所以在完成事件偵聽器之前,就不能處理額外的UI 事件。這就會使程式看來似乎停滯了,讓使用者不知所措。
要避免使UI 延遲回應,事件偵聽器應該把較長的任務放到另一個執行緒中,這樣AWT 執行緒在任務的執行過程中就可以繼續處理UI 事件(包括取消正在執行的長時間運行任務的請求)。
利用多處理器系統
多重處理器(MP)系統比過去更普及了。以前只能在大型資料中心和科學計算設施中找到它們。現在許多低階伺服器系統 ― 甚至是一些桌上型電腦系統 ― 都有多個處理器。
現代作業系統,包括 Linux、Solaris 和 Windows NT/2000,都可以利用多個處理器並調度執行緒在任何可用的處理器上執行。
調度的基本單位通常是線程;如果某個程式只有一個活動的線程,它一次只能在一個處理器上運行。如果某個程式有多個活動線程,那麼可以同時調度多個線程。在精心設計的程式中,使用多個執行緒可以提高程式吞吐量和效能。
簡化建模
在某些情況下,使用執行緒可以使程式編寫和維護起來更簡單。考慮一個仿真應用程序,您要在其中模擬多個實體之間的交互作用。給每個實體一個自己的執行緒可以使許多模擬和對應用程式的建模大大簡化。
另一個適合使用單獨執行緒來簡化程式的範例是在一個應用程式有多個獨立的事件驅動的元件的時候。例如,一個應用程式可能有這樣一個元件,該元件在某個事件之後用秒數倒數計時,並更新螢幕顯示。與其讓一個主循環定期檢查時間並更新顯示,不如讓一個執行緒什麼也不做,一直休眠,直到某一段時間後,更新螢幕上的計數器,這樣更簡單,而且不容易出錯。這樣,主執行緒就根本無需擔心計時器。
非同步或後台處理
伺服器應用程式從遠端來源(如套接字)取得輸入。當讀取套接字時,如果目前沒有可用數據,那麼對 SocketInputStream.read() 的呼叫將會阻塞,直到有可用數據為止。
如果單執行緒程式要讀取套接字,而套接字另一端的實體並未發送任何數據,那麼該程式只會永遠等待,而不執行其它處理。相反,程式可以輪詢套接字,查看是否有可用數據,但通常不會使用這種做法,因為會影響效能。
但是,如果您建立了一個執行緒來讀取套接字,那麼當這個執行緒等待套接字中的輸入時,主執行緒就可以執行其它任務。您甚至可以建立多個線程,這樣就可以同時讀取多個套接字。這樣,當有可用資料時,您會迅速得到通知(因為正在等待的執行緒被喚醒),而不必經常輪詢以檢查是否有可用資料。使用線程等待套接字的程式碼也比輪詢更簡單、更不易出錯。
簡單,但有時有風險
雖然 Java 執行緒工具非常容易使用,但當您建立多執行緒程式時,應該盡量避免一些風險。
當多個執行緒存取相同資料項(如靜態欄位、可全域存取物件的實例欄位或共用集合)時,需要確保它們協調了對資料的訪問,這樣它們都可以看到資料的一致視圖,而且相互不會幹擾另一方的變更。為了實現這個目的,Java 語言提供了兩個關鍵字:synchronized 和 volatile。我們稍後會在本教程中研究這些關鍵字的用途和意義。
當從多個執行緒存取變數時,必須確保對該存取正確地進行了同步。對於簡單變量,將變數宣告成 volatile 也許就足夠了,但在大多數情況下,需要使用同步。
如果您將要使用同步來保護對共享變數的訪問,那麼必須確保在程式中所有訪問該變數的地方都使用同步。
不要做過頭
雖然執行緒可以大幅簡化許多類型的應用程序,過度使用執行緒可能會危及程式的效能及其可維護性。線程消耗了資源。因此,在不降低效能的情況下,可以創建的執行緒的數量是有限制的。
尤其在單處理器系統中,使用多個執行緒不會使主要消耗 CPU 資源的程式運作得更快。
範例:使用一個執行緒用於計時,並使用另一個執行緒完成工作
#以下範例使用兩個執行緒,一個用於計時,一個用於執行實際工作。主執行緒使用非常簡單的演算法計算素數。
在它啟動之前,它會建立並啟動一個計時器線程,這個線程會休眠十秒鐘,然後設定一個主線程要檢查的標誌。十秒鐘之後,主線程將停止。請注意,共享標誌被聲明成 volatile。
/** * CalculatePrimes -- calculate as many primes as we can in ten seconds */ public class CalculatePrimes extends Thread { public static final int MAX_PRIMES = 1000000; public static final int TEN_SECONDS = 10000; public volatile boolean finished = false; public void run() { int[] primes = new int[MAX_PRIMES]; int count = 0; for (int i=2; count<MAX_PRIMES; i++) { // Check to see if the timer has expired if (finished) { break; } boolean prime = true; for (int j=0; j<count; j++) { if (i % primes[j] == 0) { prime = false; break; } } if (prime) { primes[count++] = i; System.out.println("Found prime: " + i); } } } public static void main(String[] args) { CalculatePrimes calculator = new CalculatePrimes(); calculator.start(); try { Thread.sleep(TEN_SECONDS); } catch (InterruptedException e) { // fall through } calculator.finished = true; } }
小結
Java 語言包含了內建在語言中的功能強大的執行緒工具。您可以將執行緒工具用於:
增加GUI 應用程式的回應速度
利用多處理器系統
當程式有多個獨立實體時,簡化程式邏輯
在不阻塞整個程式的情況下,執行阻塞I/O
當使用多個執行緒時,必須謹慎,遵循在執行緒之間共享資料的規則,我們將在共享對資料的存取中討論這些規則。所有這些規則歸結為一條基本原則:不要忘了同步。
以上是java線程有什麼用的詳細內容。更多資訊請關注PHP中文網其他相關文章!