一、前言
用Java編寫多線程程序已經是一個非常簡單的事了,不過與其它多線程系統相比,一些高級特性在Java中仍然不具備,然而在J2SE5.0中這一切將會改變。J2SE5.0增加大量的線程相關類使得編寫多線程程序更加容易!
二、線程池-Thread Pools
線程庫的基本思想簡單的講就是,一個線程庫中擁有一定數量的線程,當有任務要執行時,就從線程庫中找一個空閒的線程來執行這個任務,任務執行完後,該線程返回線程庫等待下一個任務;如果線程庫中沒有空閒的線程來執行該任務,這時該任務將要等待直到有一個空閒的線程來執行它。這聽起來有點不爽,那麼我們為什麼還要使用線程庫呢?
三、使用線程庫的三大理由
重用線程能夠獲得性能上的好處。在多線程環境中,創建一個線程要花很高的代價。線程庫使得線程能夠被重用,當有大量任務要執行時,線程庫避免了不斷創建與銷毀線程所帶來的系統開銷,使用得你的程序整體運行效率得到了一定程度的提高。
還有一個很重要的原因就是,線程庫考慮到了較好的程序設計。如果有大量任務要執行,如果不用線程庫,你不得不不重復創建一個線程、管理這個線程的生命同期的代碼。斷重復這些步驟是一件很乏味的工作,因為這與我們的業務邏輯(任務)無關。使用線程庫,這一切將由線程庫代為管理,你只需關注於你的商務邏輯,當要執行一個任務時,你只需簡單的創建一個任務,並把它丟給線程庫去執行就OK。
最後一個也是最為重要的一個:當有大量任務需要同時執行時,線程庫能夠帶來重大的性能提升。這一點看上去與第一點相似,事實上,任何時候活動的線程都比CPU數量多得多,因此線程庫能夠充分利用CPU的時間片,使得程序看去上運行較快且高效。
四、如何使用線程庫
在使用線程庫需要做兩件事:創建任務及建立線程庫本身。
一個任務是一個實現了Runnable或Callable接口的對象。
線程庫則基於Executor接口。
Java.util.concurrent.Executor
△
┆
Java.util.concurrent.ExecutorService
△
│
Java.util.concurrent.ThreadPoolExecutor
package Java.util.concurrent;
public class ThreadPoolExecutor implements ExecutorService ...{
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
}
五、兩個重要概念Sizes與Queues
Size
一個線程庫創建後,其的大小在最小值(corePoolSize)與最大值(maximumPoolSize)之間,運行時可根據getPoolSize()確定其當前的大小。
Queue
隊列用於存放等候執行的任務。
下面一步步分析這兩個值在線程庫的工作中是如何運作:
1. ThreadPoolExecutor tpe = new ThreadPoolExecutor(M, N, TIMEOUT, TimeUnit.MILLISECONDS,
2. new LinkedBlockingQueue<Runnable>());
3.
4. Task[] tasks = new Task[nTasks];
5. for (int i = 0; i < nTasks; i++) {
6. tasks[i] = new Task(n, "Task " + i);
7. tpe.execute(tasks[i]);
8. }
9. tpe.shutdown();
行1-2構造了一個線程庫,M個core threads和N maximum threads,這時實際上並沒有線程被創建。(可能過prestartAllCoreThreads() 和 prestartCoreThread() 方法分別預先創建)
第7行 一個任務被加入到線程庫,這時下列5種情況之一將會發生:
如果庫中的線程數少於M,線程庫將立即啟動一個新的線程來運行這個任務。即使庫中有空閒的線程,仍然會產生一個新線程直到數量到達M。
如果庫中的線程數在M和N之間,並且只少有一個是空閒線程,那麼任務將由這個空閒線程執行。
如果庫中的線程數在M和N之間,並且沒有空閒線程,這時庫會檢查存在的工作隊列,如果任務能夠放置在隊列中而不被阻塞,那麼任務就會放置在該列隊中,不會有新的線程啟動。
如果庫中的線程數在M和N之間,並且沒有空閒線程,且任務不能無阻塞地加入到隊列中,這時庫會開始一個新線程來運行這個任務。
如果庫中的線程數量已到達N且沒有空閒線程,這時庫將會試著放置新任務到一個隊列。如果該隊列已到達它的最大大小,任務將會被拒絕,添加失敗,否則任務將被接受等待有空閒的線程來運行它。
一個任務執行完成,運行這個任務的線程將去運行隊列中的下一個任務,如果隊列中沒有任務,將會發生下面兩種情況之一:
如果庫中的線程數大於M,線程將等待一個隊列中有新的任務。如果隊列中的新任務沒有超時,該線程將運行這個任務,否則線程將退出以減少庫中的線程數量。超時值是在新構造線程庫時指定的TIMEOUT,如果TIMEOUT為0,不管庫的最小大小(corePoolSize)是多少,線程執行完任務後總是退出。
如果庫中的線程數量等於M或小於M,該線程將不確定地被阻塞以等待一個新的任務被加入到隊列(除非TIMEOUT=0,它將退出),當它有效時就會運行這個新任務。