Java線程池策略
線程池在Java開發中隨處可見,其執行策略可以總結如下:
當提交一個新任務到線程池時:
- 判斷核心線程數是否已滿,未滿則創建一個新的線程來執行任務
- 否則判斷工作隊列是否已滿,未滿則加入隊列
- 否則判斷線程數是否以達到最大線程,沒有則創建一個新的線程來執行任務
- 否則交給飽和策略來處理
源碼分析就不展開了。
一般來說,我們建議使用ThreadPoolExecutor來創建線程池,見如下的構造函數:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
每個參數具體是什么含義,注釋里面已經描述的很清楚了,通過這些參數的配和置,來創建我們所需要的線程池。
Executors中通過靜態工廠方法提供了幾種基本的線程池創建方法,這里取兩種比較典型的簡單介紹一下:
可重用固定線程數的線程池
可重用固定線程數的線程池即為FixedThreadPool.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
corePoolSize和maximumPoolSize設置成相同,keepAliveTime設置為0L。也就是說當有新任務時,
如果線程數線程數corePoolSize創建新的線程,而空閑時多余的線程不會被回收。
FixedThreadPool隊列配置為無界隊列,有可能大量任務堆積,撐爆內存。
按需創建的線程池
根據需要創建新線程的線程池即為CachedThreadPool。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
corePoolSize 為0,maximumPoolSize為Integer.MAX_VALUE.keepAliveTime設置為60s, 空閑線程超過60秒后會被回收。但是使用沒有容量的SynchronousQueue
做為工作隊列。
這意味著如果提交任務的速度大于任務處理速度,會不斷的創建新的線程,甚至耗盡CPU和內存資源。
這兩種線程池的配置都太絕對了,因此建議使用ThreadPoolExecutor來創建線程池,限定更多的參數配置,以符合業務,資源的要求。比如:
- 對于FixedThreadPool,我們可以限制隊列的大小,避免任務的無限堆積。
- 對于CachedThreadPool,我們可以限制maximumPoolSize,避免線程的無限創建。
但是,我們能否有一種全新的策略,
- 對于可重用固定線程數的線程池,有固定數量的線程,也可以有一定數量線程擴展的能力,畢竟有些業務場景不能容忍延時,無法放入隊列。
- 對于按需創建的線程池,限定了最大線程數后,也可以有一定的隊列緩存能力。
這樣就需要一種新的線程池策略,使我們可以先優先擴充線程到maximumPoolSize,再offer到queue,否則執行拒絕邏輯。感謝開源,在tomcat和matan中的源碼都發現了這種實現,分享之:
NewThreadExecutor
首先,自定義實現一個隊列,如果線程還未達到maximumPoolSize,拒絕將任務加入隊列,這里選用LinkedTransferQueue是因為其有更好的性能。
public class TaskQueue extends LinkedTransferQueue<Runnable> {
private transient volatile NewThreadExecutor executor;
public TaskQueue() {
super();
}
public void setNewThreadExecutor(NewThreadExecutor threadPoolExecutor) {
this.executor = threadPoolExecutor;
}
public boolean offer(Runnable o) {
int poolSize = executor.getPoolSize();
// we are maxed out on threads, simply queue the object
if (poolSize == executor.getMaximumPoolSize()) {
return super.offer(o);
}
// we have idle threads, just add it to the queue
// note that we don't use getActiveCount(), see BZ 49730
if (executor.getSubmittedTasksCount() <= poolSize) {
return super.offer(o);
}
// if we have less threads than maximum force creation of a new
// thread
if (poolSize < executor.getMaximumPoolSize()) {
return false;
}
// if we reached here, we need to add it to the queue
return super.offer(o);
}
public boolean force(Runnable o, long timeout, TimeUnit unit) {
if (executor.isShutdown()) {
throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
}
// forces the item onto the queue, to be used if the task is rejected
return super.offer(o, timeout, unit);
}
}
然后繼承實現ThreadPoolExecutor類,覆寫其execute方法,源碼如下
public class NewThreadExecutor extends ThreadPoolExecutor {
private AtomicInteger submittedTasksCount;
private int maxSubmittedTaskCount;
public NewThreadExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
int queueCapacity,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, new TaskQueue(), threadFactory, handler);
((TaskQueue) getQueue()).setNewThreadExecutor(this);
submittedTasksCount = new AtomicInteger(0);
maxSubmittedTaskCount = queueCapacity + maximumPoolSize;
}
public void execute(Runnable command) {
int count = submittedTasksCount.incrementAndGet();
// There is no length limit for the LinkedTransferQueue
if (count > maxSubmittedTaskCount) {
submittedTasksCount.decrementAndGet();
getRejectedExecutionHandler().rejectedExecution(command, this);
}
try {
super.execute(command);
} catch (RejectedExecutionException rx) {
// there could have been contention around the queue
if (!((TaskQueue) getQueue()).force(command, 0, TimeUnit.MILLISECONDS)) {
submittedTasksCount.decrementAndGet();
getRejectedExecutionHandler().rejectedExecution(command, this);
}
} catch (Throwable t) {
submittedTasksCount.decrementAndGet();
throw t;
}
}
public int getSubmittedTasksCount() {
return this.submittedTasksCount.get();
}
protected void afterExecute(Runnable r, Throwable t) {
submittedTasksCount.decrementAndGet();
}
}
在某些場景下,比如依賴了遠程資源而非CPU密集型的任務,可能更適合使用這樣策略的線程池。