線程池

1.使用線程池的好處和缺點

  1. 通過重複利用已創建的線程, 減少在創建和銷燬線程上所花的時間以及系統資源的開銷。
  2. 提高響應速度。 當任務到達時, 任務可以不需要等到線程創建就可以立即行。
  3. 提高線程的可管理性。 使用線程池可以對線程進行統一的分配和監控。
  4. 如果不使用線程池, 有可能造成系統創建大量線程而導致消耗完系統內存。

雖然線程池是構建多線程應用程序的強大機制, 但使用它並不是沒有風險的。

  1. 線程池的大小。 多線程應用並非線程越多越好, 需要根據系統運行的軟硬件環境以及應用本身的特點決定線程池的大小。 一般來說, 如果代碼結構合理的話,線程數目與CPU數量相適合即可。如果線程運行時可能出現阻塞現象,可相應增加池的大小;如有必要可採用自適應算法來動態調整線程池的大小,以提高CPU的有效利用率和系統的整體性能。
  2. 併發錯誤。 多線程應用要特別注意併發錯誤, 要從邏輯上保證程序的正確性, 注意避免死鎖現象的發生。
  3. 線程泄漏。 這是線程池應用中一個嚴重的問題, 當任務執行完畢而線程沒能返回池中就會發生線程泄漏現象。

2.ThreadPoolExecutor

ThreadPoolExecutor是線程池的真實實現,它的構造方法提供了一系列參數來配置線程池。

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
參數 說明
corePoolSize 線程池維護線程的最少數量
maximumPoolSize 線程池維護線程的最大數量
keepAliveTime 線程池維護線程所允許的空閒時間
unit 線程池維護線程所允許的空閒時間的單位
workQueue 線程池所使用的緩衝隊列
handler 線程池對拒絕任務的處理策略

當一個任務通過execute(Runnable)方法欲添加到線程池時:

  • 如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
  • 如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
  • 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
  • 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。

也就是處理任務的優先級爲:

  • 核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
  • 當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。

unit可選的參數爲java.util.concurrent.TimeUnit中的幾個靜態屬性:

NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。

workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue

handler有四個選擇:

參數 說明
ThreadPoolExecutor.AbortPolicy() 拋出java.util.concurrent.RejectedExecutionException異常
ThreadPoolExecutor.CallerRunsPolicy() 重試添加當前的任務,他會自動重複調用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy() 拋棄舊的任務
ThreadPoolExecutor.DiscardPolicy() 拋棄當前的任務

使用:

public class TestThreadPool {
    private static int produceTaskSleepTime = 2;
    private static int produceTaskMaxNumber = 10;

    public static void main(String[] args) {
        // 構造一個線程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 1; i <= produceTaskMaxNumber; i++) {
            try {
                // 產生一個任務,並將其加入到線程池
                String task = "task@ " + i;
                threadPool.execute(new ThreadPoolTask(task));

                // 便於觀察,等待一段時間
                Thread.sleep(produceTaskSleepTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 線程池執行的任務
 */
class ThreadPoolTask implements Runnable, Serializable {
    private static final long serialVersionUID = 0;
    private static int consumeTaskSleepTime = 2000;
    // 保存任務所需要的數據
    private Object threadPoolTaskData;

    ThreadPoolTask(Object tasks) {
        System.out.println("生成子線程計算任務: " + tasks);
        this.threadPoolTaskData = tasks;
    }

    public void run() {
        // 處理一個任務,這裏的處理方式太簡單了,僅僅是一個打印語句
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子線程計算任務: " + threadPoolTaskData + " 執行完成!");
        try {
            // //便於觀察,等待一段時間
            Thread.sleep(consumeTaskSleepTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
        threadPoolTaskData = null;
    }
}

線程池分類

Executors下定義了幾個線程池使用

1. newFixedThreadPool

  • 是一種固定的線程池,不會被回收,除非線程池被關閉了。當所有的線程都處於活動狀態時,新任務就處於等待狀態,知道線程池空閒下來。
  • 只有核心線程,沒有超市機制。
  • 任務隊列也沒有大小限制。
  • 任務隊列沒有大小限制
  • 由於只有核心線程,並且不會被回收,這就意味着它能快速地相應界面請求。
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

2. newCachedThreadPool

  • 沒有核心線程,比較適合執行大量的好事較少的任務
  • 緩存型池子,先查看池中有沒有以前建立的線程,如果有,就 reuse 如果沒有,就建一個新的線程加入池中
  • 緩存型池子通常用於執行一些生存期很短的異步型任務。因此在一些面向連接的daemon型SERVER中用得不多。但對於生存期短的異步任務,它是 Executor 的首選。
  • 能 reuse 的線程,必須是 timeoutIDLE內的池中線程,缺省timeout是60s,超過這個IDLE時長,線程實例將被終止及移出池
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

3. newScheduledThreadPool

  • 核心線程數固定的,非核心線程數沒有限制,並且當非核心線程閒置的時候回唄立即回收。
  • 主要用於執行定時任務和具有固定週期的重複任務
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
 public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
 }

4. newSingleThreadExecutor

  • 只有一個核心線程,確保所有任務都在同一個線程中按順序執行。
  • 統一所有的外界任務到一個線程中,這使得在這些任務之間不需要處理線程同步的問題。
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章