定義
線程池,就是一個線程的池子,裏面有若干線程,它們的目的就是執行提交給線程池的任務,執行完一個任務後不會退出,而是繼續等待或執行新任務執行完一個任務後不會退出,而是繼續等待或執行新任務。
線程池主要是由兩個概念組成:一個是任務隊列;另一個是工作者線程。工作者線程主體就是一個循環,循環從隊列中接受任務並執行,任務隊列保存待執行的任務。
線程池的優點是顯而易見的:
(1) 它可以重用線程,避免線程創建的開銷。
(2)任務過多時,通過炮隊避免創建過多的線程,減少系統資源消耗和競爭,確保任務有序完成。
理解線程池的一些參數
主要的構造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
參數corePoolSize、maximumPoolSize、keepAliveTime、unit用於控制線程池中線程的個數,workQueue 表示任務隊列,threadFactor 用於對創建的線程進行一些配置,handler 表示任務拒絕策略。
線程池大小
maximumPoolSize 表示線程池的最多線程數,線程的個數會動態變化,但這是最大值,不管有多少任務,都不會創建比這個值最大的線程個數。
corePoolSize 表示線程池中的核心線程個數,不過,並不是一開始就創建這麼多線程,剛創建一個線程池後,實際上並不會創建任何線程。
一般情況下,有新任務到來的時候,如果當前線程個數小於corePoolSize,就會創建一個新線程來執行該任務,需要說明的是,即使其他線程現在也是空閒的,也會創建新的線程。不過,如果線程個數大於等於corePoolsize,那就不會立即創建新線程了,它會先嚐試排隊,需要強調的是,它是“嘗試排隊”,而不是“阻塞等待” 入隊,如果隊列滿了或其他原因不能立即入隊,它就不會排隊,而是檢查線程的個數是否達到了maximumPoolSize ,如果沒有,就會繼續創建線程,直到線程數達到maximumPoolSize.
keepAliveTime 的目的是爲了釋放多餘的線程資源,它表示,當線程池中的線程個數大於corePoolSize時,額外空閒線程的存活時間。也就是說,一個非核心線程,在空閒等待新任務時,會有一個最長等待時間,即keepAliveTime,如果到了時間還是沒有新任務,就會被終止。如果該值爲0,則表示所有線程都不會超時終止。
隊列
ThreadPoolExecutor 要求的隊列類型是阻塞隊列BlockingQueue。
比如:
LinkedBlockingQueue:基於鏈表的阻塞隊列,可以指定最大長度,但默認是無界的。
ArrayBlockingQueue:基於數組的有界阻塞隊列。
PriorityBlockingQueue:基於堆的無界阻塞優先級隊列。
SynchronousQueue:沒有實際存儲空間的同步阻塞隊列。
如果用的是無界隊列,需要強調的是,線程個數最多隻能達到corePoolSize,到達corePoolSize後,新的任務總會排隊,參數maximumPoolSize 也就沒有意義了。
對於SynchronousQueue,我們知道,它沒有實際存儲元素的空間,當嘗試排隊時,只有正好有空閒線程在等待接受任務時,纔會入隊成功,否則,總是會創建新線程,直到達到maximumPoolSize。
任務拒絕策略
如果隊列有界,且maximumPoolSize 有限,則當隊列排滿,線程個數也達到了了maximumPoolSize,這時,新任務來了,如何處理呢?此時,會觸發線程池的任務拒絕策略。
提交任務的方法(execute/submit/invokeAll)會拋出異常,類型爲RejectedException。
ThreadPoolExecutor實現了四種處理方式:
(1)ThreadPoolExecutor.AbortPolicy:這就是默認的方式沒跑出異常。
(2)ThreadPoolExecutor.DiscardPolicy:靜默處理,忽略新任務,不拋出異常,也不執行。
(3)ThreadPoolExecutor.DiscardOldestPolicy: 將等待時間最長的任務扔掉,然後自己排隊。
(4)ThreadPoolExecutor.CallerRunsPolicy:在任務提交者線程中執行任務,而不是交給線程池中線程執行。
它們都是ThreadPoolExecutor 的public靜態內部類,都實現了RejectedExecutionHandler接口,這個接口的定義爲:
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
默認的RejectedExecutionHandler是一個AbortPolicy 實例。如下:
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
拒絕策略只有在隊列有界,且maximumPoolSize 有限的情況下才會觸發。如果隊列無界,服務不了的任務總會排隊,但這不一定是期望的結果,因爲請求處理隊列可能會消耗非常大的內存,甚至引發內存不夠的異常,如果隊列有界,但maximumPoolSize 無限,可能會創建過多的線程,佔滿CPU和內存,使得任何任務都難以完成。所以,在任務量非常大的場景中,讓拒絕策略有機會執行是保證系統穩定運行很重要的方面。
線程工廠
線程池還可以接受一個參數,ThreadFactory。它還是一個接口,定義爲:
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
根據Runnable 創建一個Thread。 ThreadPoolExecutor的默認實現是Executors類中的靜態內部類DefaultThreadFactory,主要就是創建一個線程,給線程設置一個名稱,設置daemon屬性爲false,設置線程優先級爲標準默認優先級,線程名稱的格式爲:pool-<線程池編號>-thread-<線程編號>。如果需要自定義一些線程的屬性,比如名稱,可以實現自定義的ThreadFactory。
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
核心線程的特殊配置:
線程個數小於等於corePoolSize時,我們稱這些線程爲核心線程,默認情況下:
(1) 核心線程不會預先創建,只有當有任務時,纔會創建。
(2)核心線程不會因爲空閒而被終止,keepAliveTime 參數不適用於它。
//預先創建所有線程
public int prestartAllCoreThreads()
// 創建一個核心線程,如果所有核心線程都已創建,則返回false
public boolean prestartCoreThread()
// 如果參數爲true,則keepAliveTime 參數也適用於核心線程
public void allowCoreThreadTimeOut(boolean value)
工廠類Executors
提供了一些靜態的工廠方法,可以方便地預創建一些線程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
只使用一個線程,使用無界隊列LinkedBlockingQueue,線程創建後不會超時終止,該線程順序執行所有任務。該線程池適用於需要確保所有任務被順序執行的場合。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
使用固定數目的n個線程,使用無界隊列LinkedBlockingQueue,線程創建後,不會超時終止。和newSingleThreadExecutor一樣,由於是無界隊列,如果排隊任務過多,可能會消耗過多的內存。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
它的corePoolSize爲 0 ,maximumPoolSize 爲Integer.Max_Value, keepAliveTime 是60秒,隊列爲SynchronousQueue。它的含義是:當新任務到來時,如果正好有空閒線程在等待任務,則其中一個空閒線程接受該任務,否則就總是創建一個新線程,創建的總線程個數不受限制,對任一空閒線程,如果60秒內沒有新任務,就終止。
實際中,應該使用newFixedThreadPool 還是newCachedThreadPool呢?
在系統負載很高的情況下,newFixedThreadPool 可以通過隊列對新任務排隊,保證有足夠的資源處理實際的任務,而newCachedThreadPool 會爲每個任務創建一個線程,導致創建過多的線程競爭CPU和內存資源,使得任何實際任務都難以完成,這時,newFixedThreadPool 更爲適用。
不過,如果系統負載不是太高,單個任務的執行時間也比較短,newCachedThreadPool 的效率可能更高,因爲任務可以不經排隊,直接交給某一個空線程。
在系統負載可能極高的情況下,兩者都不是好的選擇,newFixedThreadPool的問題是隊列過長,而newCachedThreadPool的問題是線程過多,這時,應根據具體情況自定義ThreadPoolExecutor,傳遞合適的參數。
線程池的死鎖
任務之間有依賴,這種情況可能會出現死鎖。比如任務A,在它的執行過程中,它給同樣的任務執行服務提交了一個任務B,但需要等待任務B結束。
如果任務A是提交了一個單線程池,一定會出現死鎖,A在等待B的結果,而B在隊列中等待調度。如果是提交給了一個限定線程個數的線程池,也能因線程數限制出現死鎖。
怎麼解決這種問題呢?可以使用newCachedThreadPool創建線程池,讓線程數不受限制。另一個解決方法是使用SynchronousQueue,它可以避免死鎖,怎麼做到的呢?對於普通隊列,入隊只是把任務放到了隊列中,而對於SynchronousQueue來說,入隊成功就意味着已有線程接受處理,如果入隊失敗,可以創建更多線程直到maximumPoolSize,如果達到了maximumPoolSize,會觸發拒絕機制,不管怎麼樣,都不會死鎖。