Java線程池策略

Java線程池策略

線程池在Java開發中隨處可見,其執行策略可以總結如下:

當提交一個新任務到線程池時:

  1. 判斷核心線程數是否已滿,未滿則創建一個新的線程來執行任務
  2. 否則判斷工作隊列是否已滿,未滿則加入隊列
  3. 否則判斷線程數是否以達到最大線程,沒有則創建一個新的線程來執行任務
  4. 否則交給飽和策略來處理

源碼分析就不展開了。

一般來說,我們建議使用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密集型的任務,可能更適合使用這樣策略的線程池。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容