標籤:
From Thinking in Java 4th Edition
並發
線程可以驅動任務,因此你需要一種描述任務的方式,這可由Runnable介面來提供。
要想定義任務,只需要實現Runnable介面,並編寫run()方法,使得該任務可以執行你的命令。
public class LiftOff implements Runnable {protected int countDown = 10;// Defaultprivate static int taskCount = 0;private final int id = taskCount++;public LiftOff() {}public LiftOff(int countDown){this.countDown = countDown;}public String status(){return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!") + "). ";}public void run(){while(countDown-- > 0){System.out.println(status());Thread.yield();}}}
從Runnable匯出一個類時,它必須具有run()方法,但是它不會產生任何內在的線程能力。要實現線程行為,你必須顯式地將一個任務附著到線程上。
將Runnable對象轉變為工作任務的傳統方式是把它提交給一個Thread構造器:
public class BasicThread {public static void main(String[] args){Thread t = new Thread(new LiftOff());t.start();System.out.println("Waiting for LiftOff");}} /* Output:Waiting for LiftOff#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!), */
Thread構造器只需要一個Runnable對象。
1. 調用Thread對象的start()方法為該線程執行必要的初始化操作。
2. 調用Runnable對象的run()方法,以便在這個新線程中啟動該任務。
從輸出可以看出start()方法迅速返回了,因為"Waiting for LiftOff"訊息在倒計時完成之前就出現了。
實際上你產生的是對LiftOff.run()方法的調用,並且這個方法還沒完成,但是因為LiftOff.run()是由不同的線程執行的,因此你仍舊可以執行main()線程中的其他動作。
如果添加更多的線程去驅動更多的任務,就可以看到所有任務彼此之間是如何呼應的:
public class MoreBasicThreads {public static void main(String[] args){for(int i = 0; i < 5; ++i)new Thread(new LiftOff()).start();System.out.println("Waiting for LiftOff");}} /* Output:Waiting for LiftOff#3(9), #1(9), #3(8), #1(8), #3(7), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #4(9), #4(8), #4(7), #4(6), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(LiftOff!), #0(9), #4(5), #3(6), #4(4), #3(5), #4(3), #3(4), #4(2), #3(3), #4(1), #3(2), #4(LiftOff!), #0(8), #3(1), #0(7), #3(LiftOff!), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),*/
使用Executor
Executor在用戶端和任務執行之間提供了一個間接層;與用戶端直接執行任務不同,這個中介對象將執行任務。
Executor用來代替MoreBasicThreads.java中顯式建立Thread對象。
ExecutorService知道如何構建上下文來執行Runnable對象。
import java.util.concurrent.*;public class CachedThreadPool {public static void main(String[] args){ExecutorService exec = Executors.newCachedThreadPool(); // No space between new and Cachedfor(int i = 0; i < 5; ++i)exec.execute(new LiftOff());exec.shutdown();}} /* Output:#0(9), #2(9), #0(8), #1(9), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!), #2(8), #1(8), #3(9), #2(7), #1(7), #3(8), #2(6), #1(6), #3(7), #2(5), #2(4), #4(9), #3(6), #1(5), #3(5), #1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #4(8), #2(3), #4(7), #4(6), #3(4), #4(5), #3(3), #4(4), #3(2), #4(3), #3(1), #4(2), #2(2), #4(1), #4(LiftOff!), #3(LiftOff!), #2(1), #2(LiftOff!),*/
上例中,CachedThreadPool將為每個任務都建立一個線程。
ExecutorService對象是使用靜態Executor方法建立的,這個方法可以確定其Executor類型。
常見的情況是,Executor被用來建立和管理系統中的所有的任務。
對shutdown()方法的調用可以防止新任務被提交給這個Executor,當前任務(本例中為main的線程)將繼續運行在shutdown()被調用之前提交的所有任務。
下面的程式展示了CachedThreadPool替換為不同類型的Executor。FixedThreadPool使用了有限的線程集來執行所提交的任務
import java.util.concurrent.*;public class FixedThreadPool {public static void main(String[] args){// Constructor argument is number of threads:ExecutorService exec = Executors.newFixedThreadPool(5);for(int i = 0; i < 5; ++i)exec.execute(new LiftOff());exec.shutdown();}} /* Output:#0(9), #2(9), #4(9), #0(8), #2(8), #3(9), #1(9), #1(8), #0(7), #4(8), #0(6), #4(7), #0(5), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(LiftOff!), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #3(8), #2(7), #0(4), #3(7), #2(6), #0(3), #3(6), #2(5), #0(2), #3(5), #0(1), #3(4), #2(4), #0(LiftOff!), #3(3), #2(3), #3(2), #2(2), #3(1), #2(1), #3(LiftOff!), #2(LiftOff!),*/
有了FixedThreadPool,你就可以一次性預先執行代價高昂的線程分配。這可以節省時間,因為你不用為每個任務都固定地付出建立線程的開銷。
SingleThreadExecutor就像是線程數量為1的FixedThreadPool。如果向SingleThreadExecutor提交了多個任務,那麼這些任務將排隊,每個任務都會在下一個任務開始之前結束,所有的任務將使用相同的線程。
下例中可以看到每個任務都是按照它們被提交的順序、並且是在下一個任務開始之前完成的。因此SingleThreadExecutor會序列化所有提交給它們的任務,並會維護它自己(隱藏)的懸掛任務。
import java.util.concurrent.*;public class SingleThreadExecutor {public static void main(String[] args){ExecutorService exec = Executors.newSingleThreadExecutor();for(int i = 0; i < 5; ++i)exec.execute(new LiftOff());exec.shutdown();}} /* Output:#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!), #1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(LiftOff!), #3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2), #3(1), #3(LiftOff!), #4(9), #4(8), #4(7), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(LiftOff!),*/
從任務中傳回值
Runnable是執行工作的獨立任務,但是它不返回任何值。如果希望能夠傳回值,則必須實現Callable介面而不是Runnable介面。
Callable是一種具有型別參數的泛型,它的型別參數表示的是從方法call()中返回的值,並且必須使用ExecutorService.submit()方法調用它:
import java.util.concurrent.*;import java.util.*;class TaskWithResult implements Callable<String> {private int id;public TaskWithResult(int id){this.id = id;}public String call() {return "result of TaskWithResult " + id;}}public class CallableDemo {public static void main(String[] args){ExecutorService exec = Executors.newCachedThreadPool();ArrayList<Future<String>> results = new ArrayList<Future<String>>();for(int i = 0; i < 10; ++i)results.add(exec.submit(new TaskWithResult(i)));for(Future<String> fs : results)try {// get() blocks until completion:System.out.println(fs.get());} catch(InterruptedException e){System.out.println(e);return;} catch(ExecutionException e) {System.out.println(e);} finally {exec.shutdown();}}} /* Output:result of TaskWithResult 0result of TaskWithResult 1result of TaskWithResult 2result of TaskWithResult 3result of TaskWithResult 4result of TaskWithResult 5result of TaskWithResult 6result of TaskWithResult 7result of TaskWithResult 8result of TaskWithResult 9*/
submit()方法會產生Future對象,它用Callable返回結果的特定類型進行了參數化 。
1. 可以用isDone()方法來查看Future是否完成
2. 任務完成時,可以調用get()方法來擷取該結果
也可以不用isDone()進行檢查就直接調用get(),這種情況下,get()將阻塞直至結果準備就緒。
休眠
影響任務行為的一種簡單方法是調用sleep(),這將使任務中止執行給定的時間。
在LiftOff類中,把yield()的調用換成sleep()將得到:
import java.util.concurrent.*;public class SleepingTask extends LiftOff {public void run(){try {while(countDown-- > 0){System.out.print(status());// Old-style// Thread.sleep(100);// Java SE5/6-style:TimeUnit.MILLISECONDS.sleep(100);}} catch(InterruptedException e) {System.err.println("Interrupted");}}public static void main(String[] args){ExecutorService exec = Executors.newCachedThreadPool();for(int i = 0; i < 5; ++i)exec.execute(new SleepingTask());exec.shutdown();}} /* Output:#0(9), #3(9), #1(9), #4(9), #2(9), #0(8), #3(8), #1(8), #2(8), #4(8), #0(7), #4(7), #2(7), #3(7), #1(7), #4(6), #0(6), #3(6), #1(6), #2(6), #4(5), #3(5), #2(5), #0(5), #1(5), #4(4), #3(4), #2(4), #1(4), #0(4), #4(3), #2(3), #3(3), #0(3), #1(3), #4(2), #2(2), #0(2), #3(2), #1(2), #4(1), #2(1), #0(1), #3(1), #1(1), #4(LiftOff!), #2(LiftOff!), #0(LiftOff!), #3(LiftOff!), #1(LiftOff!),*/
sleep()調用可以拋出InterruptedException異常,並可以看到,它在run()中捕獲。因為異常不能跨線程傳播回main(),所以你必須在本地處理所有在任務內部產生的異常。
優先順序
調度器將傾向於讓優先權最高的線程先執行,然而,這並不意味著優先權較低的線程將得不到執行(也就是說,優先權不會導致死結)。優先順序較低的線程僅僅是執行的頻率較低。
絕大多數時間裡,所有線程都應該按照預設的優先順序運行。試圖操縱線程的優先順序通常是一種錯誤。
可以用getPriority()來讀取現有線程的優先順序,並且在任何時候都可以通過setPriority()來修改它:
import java.util.concurrent.*;public class SimplePriorities implements Runnable {private int countDown = 5;private volatile double d;// No optimizationprivate int priority;public SimplePriorities(int priority){this.priority = priority;}public String toString(){return Thread.currentThread() + ": " + countDown;}public void run(){Thread.currentThread().setPriority(priority);while(true){// An expensive, interruptable operation:for(int i = 1; i < 100000; ++i){d += (Math.PI + Math.E) / (double)i;if(0 == i % 1000)Thread.yield();}System.out.println(this);if(0 == --countDown) return;}}public static void main(String[] args){ExecutorService exec = Executors.newCachedThreadPool();for(int i = 0; i < 5; ++i)exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));exec.shutdown();}}
讓步
如果知道已經完成了在run()方法的迴圈的一次迭代過程中所需的工作,就可以給線程調度機制一個暗示:你的工作已經做得差不多了,可以讓別的線程使用CPU了。這個暗示將通過yield()方法做出(不過這隻是一個暗示,沒有任何機制保證它將會被採納)。
當調用yield()時,你也在建議具有相同優先順序的其他線程可以運行。
後台線程
後台(daemon)線程,是指在程式啟動並執行時候在後台提供一種泛型服務的線程,並且這種線程並不屬於程式中不可缺少的部分。
當所有非後台線程結束時,程式也就終止了,同時會殺死進程中的所有後台線程。
反過來,只要有非後台線程還在運行,程式就不會終止:
import java.util.concurrent.*;import static net.mindview.util.Print.*;public class SimpleDaemons implements Runnable {public void run(){try {while(true) {TimeUnit.MILLISECONDS.sleep(100);print(Thread.currentThread() + " " + this);}} catch(InterruptedException e) {print("sleep() interrupted");}}public static void main(String[] args) throws Exception {for(int i = 0; i < 10; ++i){Thread daemon = new Thread(new SimpleDaemons());daemon.setDaemon(true);// Must call before start()daemon.start();}print("All daemons started");TimeUnit.MILLISECONDS.sleep(175);}} /* Output:All daemons startedThread[Thread-4,5,main] [email protected]Thread[Thread-2,5,main] [email protected]Thread[Thread-3,5,main] [email protected]Thread[Thread-8,5,main] [email protected]Thread[Thread-0,5,main] [email protected]Thread[Thread-1,5,main] [email protected]Thread[Thread-9,5,main] [email protected]Thread[Thread-7,5,main] [email protected]Thread[Thread-5,5,main] [email protected]Thread[Thread-6,5,main] [email protected]*/
SimpleDaemons.java建立了顯示的線程,以便可以設定它們的後台標誌。通過編寫定製的ThreadFactory可以定製由Executor建立的線程的屬性(後台、優先順序、名稱):
package net.mindview.util;import java.util.concurrent.*;public class DaemonThreadFactory implements ThreadFactory {public Thread newThread(Runnable r) {Thread t = new Thread(r);t.setDaemon(true);return t;}}
現在可以用一個新的DaemonThreadFactory作為參數傳遞給Executor.newCachedThreadPool():
package net.mindview.util;import java.util.concurrent.*;public class DaemonThreadFactory implements ThreadFactory {public Thread newThread(Runnable r) {Thread t = new Thread(r);t.setDaemon(true);return t;}}// Using a Thread Factory to create daemons.import java.util.concurrent.*;import net.mindview.util.*;import static net.mindview.util.Print.*;public class DaemonFromFactory implements Runnable {public void run(){try {while(true){TimeUnit.MILLISECONDS.sleep(100);print(Thread.currentThread() + " " + this);}} catch(InterruptedException e){print("Interrupted");}}public static void main(String[] args) throws Exception {ExecutorService exec = Executor.newCachedThreadPool(new DaemonThreadFactory());for(int i = 0; i < 10; ++i)exec.execute(new DaemonFromFactory());print("All daemons started");TimeUnit.MILLISECONDS.sleep(500);// Run for a while}} /* OutputAll daemons startedThread[Thread-0,5,main] [email protected]Thread[Thread-9,5,main] [email protected]Thread[Thread-7,5,main] [email protected]Thread[Thread-5,5,main] [email protected]Thread[Thread-3,5,main] [email protected]Thread[Thread-1,5,main] [email protected]Thread[Thread-8,5,main] [email protected]Thread[Thread-2,5,main] [email protected]Thread[Thread-4,5,main] [email protected]Thread[Thread-6,5,main] [email protected]Thread[Thread-0,5,main] [email protected]Thread[Thread-9,5,main] [email protected]....*/
每個靜態ExecutorService建立方法都被重載為接受一個ThreadFactory對象,而這個對象將被用來建立新的線程:
package net.mindview.util;import java.util.concurrent.*;public class DaemonThreadPoolExecutor extends ThreadPoolExecutor {public DaemonThreadPoolExecutor() {super(0, Integer.MAX_VALUE, 60L, TimeUnit, SECONDS, new SynchronousQueue<Runnable>(), new DaemonThradFactory());}}
可以通過iDaemon()方法來確定線程是否是一個後台線程。如果是一個後台線程,那麼它建立的任何線程將自動被設定成後台線程。
// Daemon threads spawn other daemon threads.import java.util.concurrent.*;import static net.mindview.util.Print.*;class Daemon implements Runnable {private Thread[] t = new Thread[10];public void run() {for(int i = 0; i < t.length; ++i){t[i] = new Thread(new DaemonSpawn());t[i].start();printnb("DaemonSpawn " + i + " started, ");}for(int i = 0; i < t.length; ++i)printnb("t[" + i + "].isDaemon() = " + t[i].isDaemon() + ", ");while(true)Thread.yield();}}class DaemonSpawn implements Runnable {public void run(){while(true)Thread.yield();}}public class Daemons {public static void main(String[] args) throws Exception {Thread d = new Thread(new Daemon());d.setDaemon(true);d.start();printnb("d.isDaemon() = " + d.isDaemon() + ", ");// Allow the daemon threads to // finish their startup processes:TimeUnit.SECONDS.sleep(1);}} /* Output:d.isDaemon() = true, DaemonSpawn 0 started, DaemonSpawn 1 started, DaemonSpawn 2 started, DaemonSpawn 3 started, DaemonSpawn 4 started, DaemonSpawn 5 started, DaemonSpawn 6 started, DaemonSpawn 7 started, DaemonSpawn 8 started, DaemonSpawn 9 started, t[0].isDaemon() = true, t[1].isDaemon() = true, t[2].isDaemon() = true, t[3].isDaemon() = true, t[4].isDaemon() = true, t[5].isDaemon() = true, t[6].isDaemon() = true, t[7].isDaemon() = true, t[8].isDaemon() = true, t[9].isDaemon() = true,*/
應該意識到後台進程在不執行finally子句的情況下就會終止其run()方法:
// Daemon threads don‘t run the finally clauseimport java.util.concurrent.*;import static net.mindview.util.Print.*;class ADaemon implements Runnable {public void run() {try {print("Starting ADaemon");TimeUnit.SECONDS.sleep(1);} catch(InterruptedException e){print("Exiting via InterruptedException");} finally {print("This should always run?");}}}public class DaemonsDontRunFinally {public static void main(String[] args) throws Exception {Thread t = new Thread(new ADaemon());t.setDaemon(true);t.start();}} /* Output:Starting ADaemon*/
目前的樣本中,都是實現了Runnable。在非常簡單的情況下,你可能會希望使用直接從Thread繼承這種可替換的方式:
public class SimpleThread extends Thread {private int countDown = 5;private static int threadCount = 0;public SimpleThread() {// Store the thread name:super(Integer.toString(++threadCount));start();}public String toString() {return "#" + getName() + "(" + countDown + "), ";}public void run() {while(true){System.out.print(this);if(0 == --countDown)return;}}public static void main(String[] args){for(int i = 0; i < 5; ++i)new SimpleThread();}} /* Output:#2(5), #2(4), #2(3), #2(2), #2(1), #4(5), #4(4), #4(3), #4(2), #4(1), #5(5), #5(4), #5(3), #5(2), #5(1), #3(5), #3(4), #3(3), #3(2), #3(1), #1(5), #1(4), #1(3), #1(2), #1(1),*/
你可以通過適當的Thread構造器為Thread對象賦予具體的名稱,這個名稱可以通過使用getName()從toString()中獲得。
另一種可能會看到的慣用法是自管理的Runnable:
// A Runnable containing its own driver Thread.public class SelfManaged implements Runnable {private int countDown = 5;private Thread t = new Thread(this);public SelfManaged() {t.start();}public String toString() {return Thread.currentThread().getName() + "(" + countDown + "), ";}public void run() {while(true){System.out.print(this);if(0 == --countDown)return;}}public static void main(String[] args){for(int i = 0; i < 5; ++i)new SelfManaged();}} /* Output:Thread-3(5), Thread-3(4), Thread-3(3), Thread-3(2), Thread-3(1), Thread-5(5), Thread-5(4), Thread-5(3), Thread-5(2), Thread-5(1), Thread-6(5), Thread-6(4), Thread-6(3), Thread-6(2), Thread-6(1), Thread-4(5), Thread-4(4), Thread-4(3), Thread-4(2), Thread-4(1), Thread-7(5), Thread-7(4), Thread-7(3), Thread-7(2), Thread-7(1),*/
這與從Thread繼承並沒有什麼特別的差異,只是文法稍微晦澀一些。但是,實現介面使得你可以繼承另一個不同的類,而從Thread繼承將不行。
注意,這個樣本中的start()是在構造器中調用的。應該意識到,在構造器中啟動線程可能會變得很有問題,因為另一個任務可能會在構造器結束之前開始執行,這意味著該任務能夠訪問處於不穩定點的對象。這是優選Executor而不是顯式地建立Thread對象的另一個原因。
有時,通過使用內部類來講線程程式碼後置在類中將會很有用:
Thinking in Java from Chapter 21