Oracle官方並發教程之線程對象

杜老師說 2022-01-07 13:33:50 阅读数:470

oracle 官方 教程 程之

原文鏈接譯文鏈接,譯者:鄭旭東

在Java中,每個線程都是Thread類的實例。並發應用中一般有兩種不同的線程創建策略。

  • 直接控制線程的創建和管理,每當應用程序需要執行一個异步任務的時候就為其創建一個線程
  • 將線程的管理從應用程序中抽象出來作為執行器,應用程序將任務傳遞給執行器,有執行器負責執行。

這一節,我們將討論Thread對象,有關Executors將在高級並發對象一節中討論。

定義並啟動一個線程

應用程序在創建一個線程實例時,必須提供需要在線程中運行的代碼。有兩種方式去做到這一點:

  • 提供一個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對象可以繼承於其他類(Java只支持單繼承,當一個類繼承與Thread類後,就無法繼承與其他類)。第二種方法更易於在簡單的應用程序中使用,但它的局限就是:你的任務類必須是Thread的子類。這個課程更加聚焦於第一種將Runnable任務和Thread類分離的方式。不僅僅是因為這種方式更加靈活,更因為它更適合後面將要介紹的高級線程管理API。 Thread類定義了一些對線程管理十分有用的的方法。在這些方法中,有一些靜態方法可以給當前線程調用,它們可以提供一些有關線程的信息,或者影響線程的狀態。而其他一些方法可以由其他線程進行調用,用於管理線程和Thread對象。我們將在下面的章節中,深入探討這些內容。

使用Sleep方法暫停一個線程

使用Thread.sleep()方法可以暫停當前線程一段時間。這是一種使處理器時間可以被其他線程或者運用程序使用的有效方式。sleep()方法還可以用於調整線程執行節奏(見下面的例子)和等待其他有執行時間需求的線程(這個例子將在下一節演示)。

在Thread中有兩個不同的sleep()方法,一個使用毫秒錶示休眠的時間,而另一個是用納秒。由於操作系統的限制休眠時間並不能保證十分精確。休眠周期可以被interrups所終止,我們將在後面看到這樣的例子。不管在任何情况下,我們都不應該假定調用了sleep()方法就可以將一個線程暫停一個十分精確的時間周期。

SleepMessages程序為我們展示了使用sleep()方法每四秒打印一個信息的例子

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()方法就會拋出該异常。由於這個應用程序並沒有定義其他的線程,所以並不用關心如何處理該异常。

中斷(Interrupts)

原文鏈接譯文鏈接譯者: 蘑菇街-小寶   校對:梁海艦
中斷是給線程的一個指示,告訴它應該停止正在做的事並去做其他事情。一個線程究竟要怎麼響應中斷請求取决於程序員,不過讓其終止是很普遍的做法。這是本文重點强調的用法。

一個線程通過調用對被中斷線程的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异常退出的方法,當拋該异常時會清除中斷狀態。不過,通過其他的線程調用interrupt()方法,中斷狀態總是有可能會立即被重新設置。

Joins

Join()方法可以讓一個線程等待另一個線程執行完成。若t是一個正在執行的Thread對象,

 t.join();

將會使當前線程暫停執行並等待t執行完成。重載的join()方法可以讓開發者自定義等待周期。然而,和sleep()方法一樣join()方法依賴於操作系統的時間處理機制,你不能假定join()方法將會精確的等待你所定義的時長。

如同sleep()方法,join()方法響應中斷並在中斷時拋出InterruptedException。

一個簡單的線程例子

下面這個簡單的例子將會把這一節的一些概念放到一起演示。SimpleThreads程序有兩個線程組成,第一個是主線程,它從創建了一個線程並等待它執行完成。如果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!"); }}

原創文章,轉載請注明: 轉載自並發編程網 – ifeve.com本文鏈接地址: Oracle官方並發教程之線程對象

FavoriteLoading添加本文到我的收藏
版权声明:本文为[杜老師說]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201071333504592.html