標籤:應該 方式 ima 也會 cti 環境 imp 常用 任務
五、睡眠和喚醒一個線程
有時,你會想要在一段特定的時間後再去中斷線程的運行。舉個例子,程式中的一個線程每一分鐘檢查一次感應器的狀態,剩餘的時間,線程應該處於閒置狀態。在這段空閑時間裡,線程不會使用電腦的任何資源。一分鐘後,線程已經準備好了,才讓JVM選擇調用它繼續執行。你可以使用 Thread 類的 sleep() 方法來達到此目的。該方法接受一個 int 型別參數表明線程掛起不啟動並執行毫秒數。當睡眠時間結束,線程轉移到可運行狀態等待JVM的調度。
TimeUnit 枚舉類的某個成員同樣具有 sleep() 方法。該方法利用了 Thread 類的 sleep() 方法,使得當前線程睡眠,但是它接受的參數的單位並不是固定為毫秒數,更加方便。
在本秘訣中,我們開發一個利用 sleep() 方法每秒輸出 Date 值的程式。
public class FileMain { public static void main(String[] args) { FileClock clock = new FileClock(); Thread thread = new Thread(clock); thread.start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); }}public class FileClock implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.printf("%s\n", new Date()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { System.out.printf("The FileClock has been interrupted"); } } } }
當你調用 sleep() 方法時,線程就會放棄佔用CPU和停止執行一段時間。在這段時間裡,它不佔用CPU時間。
當線程在睡眠中並且被中斷時,該方法立即拋出一個 InterruptedException 異常,並不會等待直到睡眠時間到達。
注意:yield() 方法也具有讓線程放棄佔用CPU的功能,不過,這個方法最好只用來表明自己可以放棄CPU佔用,JVM的規範中沒有強制要求其必須要放棄CPU佔用。這個方法一般只用在調試環境中,讓具有更高許可權的線程可以搶佔執行。
筆者總結:線程調用 sleep() 方法後,該線程處於掛起狀態,自身不會去搶佔CUP等資源來運行,但JVM內部有系統線程去感知是否該睡眠線程被中斷,從而以拋出異常方式通知睡眠線程提前喚醒來運行代碼處理此種情況。
六、等待線程的完成
某些情境下,我們可能需要等待線程的終結。舉個例子:我們的程式在順序上可能需要在執行某些步驟之前需要初始化某些資源。我們可以把初始化資源的任務作為一個單獨的線程運行,並且在主任務線程中等待它完成後再進行下一步的處理。
我們可以使用 Thread 類的 join() 方法達到該目的。當我們調用一個 Thread 對象的 join() 方法時,調用此方法的線程就會掛起,直到 Thread 對象關聯的線程執行完畢。
public class DataSourcesLoader implements Runnable{ @Override public void run() { System.out.printf("Beginning data sources loading: %s\n", new Date()); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Data sources loading has finished: %s\n", new Date()); } }public class Main { public static void main(String[] args) { DataSourcesLoader dsLoader = new DataSourcesLoader(); Thread thread1 = new Thread(dsLoader, "DataSourceThread"); NetworkConnectionsLoader ncLoader = new NetworkConnectionsLoader(); Thread thread2 = new Thread(ncLoader, "NetworkConnectionLoader"); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Main: Configuration has been loaded: %s\n", new Date()); }}public class NetworkConnectionsLoader implements Runnable{ @Override public void run() { System.out.printf("Beginning Network connection: %s\n", new Date()); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Network connection has finished: %s\n", new Date()); } }
如果你運行此樣本程式多次後就會發現,每次都是先運行 thread1 並結束後,才運行 thread2,thread2結束後才會結束 main 線程的運行。
join() 方法具有兩個類似的方法,他們分別是:
(A) join(long milliseconds)
(B) join(long milliseconds, long nanos)
這兩個方法通向會掛起調用線程直到該方法所屬的 Thread 對象關聯的線程執行結束,但是在指定的時間達到後,調用線程可以繼續運行,不用一直等待。
筆者總結:這三個方法經常用來線程之間的同步,帶參數的方法可以實現逾時不等待邏輯。
重要:本系列翻譯文檔也會在本人的公眾號(此山是我開)第一時間發布,歡迎大家關注。
Java 7 Concurrency Cookbook 翻譯 第一章 線程管理之三