每一個線程都與Thread類的實例相關聯,使用Thread
對象建立併發應用程序有兩種基本策略。html
Thread
。本節介紹Thread
對象的使用,Executors
將與其餘高級併發對象一塊兒討論。java
建立Thread
實例的應用程序必須提供將在該線程中運行的代碼,有兩種方法能夠作到這一點:git
Runnable
對象,Runnable接口定義了一個單獨的run
方法,用於包含在線程中執行的代碼,Runnable
對象被傳遞給Thread
構造函數,如HelloRunnable示例中所示:public class HelloRunnable implements Runnable { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new Thread(new HelloRunnable())).start(); } }
Thread
,Thread
類自己實現了Runnable
,儘管它的run
方法什麼都不作,應用程序能夠子類化Thread
,提供本身的run
實現,如HelloThread示例中所示:public class HelloThread extends Thread { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new HelloThread()).start(); } }
請注意,兩個示例都調用Thread.start
以啓動新線程。程序員
你應該使用哪一個語法?使用Runnable
對象的第一個語法更通用,由於Runnable
對象能夠繼承Thread
之外的類。第二個語法在簡單的應用程序中更容易使用,但受限於你的任務類必須是Thread
的後代這一事實。本課重點介紹第一種方法,該方法將Runnable
任務與執行任務的Thread
對象分開,這種方法不只更靈活,並且適用於後面介紹的高級線程管理API。github
Thread
類定義了許多對線程管理有用的方法,這些包括靜態方法,它們提供關於調用該方法的線程的信息,或影響該線程的狀態。其餘方法是從管理線程和Thread
對象的其餘線程調用的,咱們將在如下部分中研究其中一些方法。segmentfault
Thread.sleep
致使當前線程暫停執行指定的時間段,這是使處理器時間可用於應用程序的其餘線程或可能在計算機系統上運行的其餘應用程序的有效方法。sleep
方法也能夠用於調步,以下面的示例所示,和等待具備被理解爲具備時間要求的職責的另外一個線程,如稍後部分中的SimpleThreads
示例。api
提供了兩個重載版本的sleep
:一個指定毫秒的睡眠時間,一個指定納秒的睡眠時間。可是,這些睡眠時間並不能保證精確,由於它們受到底層操做系統提供的設施的限制,此外,睡眠週期能夠經過中斷終止,咱們將在後面的部分中看到。在任何狀況下,你都不能設想調用sleep
會準確地在指定的時間段內暫停該線程。併發
SleepMessages示例使用sleep
以四秒爲間隔打印消息:oracle
public class SleepMessages { public static void main(String args[]) throws InterruptedException { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; for (int i = 0; i < importantInfo.length; i++) { //Pause for 4 seconds Thread.sleep(4000); //Print a message System.out.println(importantInfo[i]); } } }
請注意,main
聲明拋出InterruptedException
,這是一個異常,當sleep
處於活動狀態時,另外一個線程中斷當前線程時,sleep
將拋出,因爲此應用程序還沒有定義另外一個致使中斷的線程,所以無需捕獲InterruptedException
。異步
中斷是指示線程應該中止正在作的事情,並執行其餘操做,由程序員決定線程如何響應中斷,但用於終止線程是很常見的,這是本課程中強調的用法。
線程經過調用Thread
對象上的interrupt來發送中斷,以便線程被中斷,爲使中斷機制正常工做,被中斷的線程必須支持本身的中斷。
線程如何支持本身的中斷?這取決於它目前正在作什麼,若是線程常常調用拋出InterruptedException
的方法,它只會在捕獲該異常後從run
方法返回。例如,假設SleepMessages
示例中的中心消息循環位於線程的Runnable
對象的run
方法中,而後能夠按以下方式修改它以支持中斷:
for (int i = 0; i < importantInfo.length; i++) { // Pause for 4 seconds try { Thread.sleep(4000); } catch (InterruptedException e) { // We've been interrupted: no more messages. return; } // Print a message System.out.println(importantInfo[i]); }
許多拋出InterruptedException
的方法(例如sleep
)被設計爲收到中斷時取消當前操做並當即返回。
若是一個線程長時間運行而不調用拋出InterruptedException
的方法呢?那麼它必須按期調用Thread.interrupted
,若是收到中斷,則返回true
,例如:
for (int i = 0; i < inputs.length; i++) { heavyCrunch(inputs[i]); if (Thread.interrupted()) { // We've been interrupted: no more crunching. return; } }
在這個簡單的例子中,代碼只是測試中斷,若是收到中斷則退出線程,在更復雜的應用程序中,拋出InterruptedException
可能更有意義:
if (Thread.interrupted()) { throw new InterruptedException(); }
這容許中斷處理代碼集中在catch
子句中。
中斷機制使用稱爲中斷狀態的內部標誌來實現,調用Thread.interrupt
設置此標誌,當線程經過調用靜態方法Thread.interrupted
來檢查中斷時,將清除中斷狀態,非靜態isInterrupted
方法,由一個線程用於查詢另外一個線程的中斷狀態,不會更改中斷狀態標誌。
按照慣例,任何經過拋出InterruptedException
退出的方法都會在執行此操做時清除中斷狀態,可是,經過另外一個線程調用中斷,老是能夠當即再次設置中斷狀態。
join
方法容許一個線程等待另外一個線程的完成,若是t
是其線程當前正在執行的Thread
對象:
t.join();
致使當前線程暫停執行,直到t
的線程終止,join
重載方法容許程序員指定等待週期,可是,與sleep
同樣,join
依賴於OS進行計時,所以你不該該設想join
將準確地等待你指定的時間。
與sleep
同樣,join
經過InterruptedException
退出來響應中斷。
如下示例彙總了本節的一些概念,SimpleThreads由兩個線程組成。第一個是每一個Java應用程序都有的主線程,主線程從Runnable
對象MessageLoop
建立一個新線程,並等待它完成,若是MessageLoop
線程須要很長時間才能完成,主線程會中斷它。
MessageLoop
線程打印出一系列消息,若是在打印完全部消息以前被中斷,MessageLoop
線程將打印一條消息並退出。
public class SimpleThreads { // Display a message, preceded by // the name of the current thread static void threadMessage(String message) { String threadName = Thread.currentThread().getName(); System.out.format("%s: %s%n", threadName, message); } private static class MessageLoop implements Runnable { public void run() { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; try { for (int i = 0; i < importantInfo.length; i++) { // Pause for 4 seconds Thread.sleep(4000); // Print a message threadMessage(importantInfo[i]); } } catch (InterruptedException e) { threadMessage("I wasn't done!"); } } } public static void main(String args[]) throws InterruptedException { // Delay, in milliseconds before // we interrupt MessageLoop // thread (default one hour). long patience = 1000 * 60 * 60; // If command line argument // present, gives patience // in seconds. if (args.length > 0) { try { patience = Long.parseLong(args[0]) * 1000; } catch (NumberFormatException e) { System.err.println("Argument must be an integer."); System.exit(1); } } threadMessage("Starting MessageLoop thread"); long startTime = System.currentTimeMillis(); Thread t = new Thread(new MessageLoop()); t.start(); threadMessage("Waiting for MessageLoop thread to finish"); // loop until MessageLoop // thread exits while (t.isAlive()) { threadMessage("Still waiting..."); // Wait maximum of 1 second // for MessageLoop thread // to finish. t.join(1000); if (((System.currentTimeMillis() - startTime) > patience) && t.isAlive()) { threadMessage("Tired of waiting!"); t.interrupt(); // Shouldn't be long now // -- wait indefinitely t.join(); } } threadMessage("Finally!"); } }