線程池個人理解和實現

進程和線程

        進程可以理解爲數據結構和內存空間上的,不具備運行能力;應用程序之所以能夠運行OS在創建進程的同時爲應用程序創建了一個主線程。

        線程可以理解爲程序中的一條執行路徑,同時線程也是cpu調度的最小單位,沒有線程存在進程就會結束OS回收資源。

爲什麼使用線程池

        在不使用線程池之前當有多個任務需要處理時,只要有任務進入就需要創建新的線程來執行,這樣看似也不錯但是我們知道線程的創建時很耗費資源的且平凡的創建線程會影響整個系統的性能,因此我們設想加入有一組已經創建好的線程處於掛起狀態,當有任務來時就喚醒其中一個進行任務的處理,執行完成後又進入掛起,這樣就避免了運行中平凡創建線程帶來的性能問題,可能有人會問線程是併發執行的嗎?這個要看cpu的物理核心或者超線程數量,至少單核心下線程也是在一個時間片內得到執行。因此線程的出現就是爲了解決程序運行中平凡創建線程帶來的性能問題。

線程池的構成

       線程池管理者,線程池既然是一組線程組成,那麼首先會有個線程來進行管理,例如線程組的創建,任務的管理,線程的調度,線程和任務的數量並不是無限大的因此設置一個閾值能夠使線程池更好的運行。

       工作線程,即處於待命的線程,有任務執行時喚醒沒有時掛起。

       任務即要做的事情,在程序中就是一個接口,因此任務具體要做的事情就不歸線程池來處理了從能任務和線程池互補影響,線程池只管調用接口,具體的事情接口自己知道。

       任務隊列,線程池中的線程數量是有限的不可能每個任務能夠立刻得到執行,因此需要一個隊列來保存這些任務等待線程池來調度這些任務;當然任務也並不是有多少就保存多少這樣內存會被耗盡最終內存溢出,因此這就出現了飽和策略。

jdk中的線程池

       Executor框架是java中的線程池實現。Executor是最頂層的接口定義,它的子類和實現主要包括ExecutorService,ScheduledExecutorService,ThreadPoolExecutor,ScheduledThreadPoolExecutor,ForkJoinPool等。其結構如下圖所示:

Executor:Executor是一個接口,其只定義了一個execute()方法:void execute(Runnable command);,只能提交Runnable形式的任務,不支持提交Callable帶有返回值的任務。
ExecutorService:ExecutorService在Executor的基礎上加入了線程池的生命週期管理,我們可以通過ExecutorService#shutdown或者ExecutorService#shutdownNow方法來關閉我們的線程池。ExecutorService支持提交Callable形式的任務,提交完Callable任務後我們拿到一個Future,它代表一個異步任務執行的結果。關於shutdown和shutdownNow方法我們需要注意的是:這兩個方法是非阻塞的,調用後立即返回,不會等待線程池關閉完成。如果我們需要等待線程池處理完成再返回可以使用ExecutorService#awaitTermination來完成。
shutdown方法會等待線程池中已經運行的任何和阻塞隊列中等待執行的任務執行完成,而shutdownNow則不會,shutdownNow方法會嘗試中斷線程池中已經運行的任務,阻塞隊列中等待的任務不會再被執行,阻塞隊列中等待執行的任務會作爲返回值返回。
ThreadPoolExecutor:是線程池中最核心的類,這裏着重說一下這個類的各個構造參數:

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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

四種類型的線程池

FixedThreadPool 定長線程池

它是一種固定大小的線程池;
corePoolSize和maximunPoolSize都爲用戶設定的線程數量nThreads;
阻塞隊列採用了LinkedBlockingQueue,它是一個無界隊列;
由於阻塞隊列是一個無界隊列,因此永遠不可能拒絕任務;
由於採用了無界隊列,實際線程數量將永遠維持在nThreads,因此maximumPoolSize和keepAliveTime將無效

CachedThreadPool 可緩存線程池

它是一個可以無限擴大的線程池;
它比較適合處理執行時間比較小的任務;
corePoolSize爲0,maximumPoolSize爲無限大,意味着線程數量可以無限大;
keepAliveTime爲60S,意味着線程空閒時間超過60S就會被殺死;
採用SynchronousQueue裝等待的任務,這個阻塞隊列沒有存儲空間,這意味着只要有請求到來,就必須要找到一條工作線程處理他,如果當前沒有空閒的線程,那麼就會再創建一條新的線程。

SingleThreadExecutor  單一線程池

它只會創建一條工作線程處理任務;

採用的阻塞隊列爲LinkedBlockingQueue;

ScheduledThreadPool  可調度的線程池

它用來處理延時任務或定時任務。

線程池流程圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章