Java 線程池全面解析

Java-五種線程池,四種拒絕策略,三種阻塞隊列

  • 三種阻塞隊列:

    BlockingQueue<Runnable> workQueue = null;
    workQueue = new ArrayBlockingQueue<>(5);//基於數組的先進先出隊列,有界
    workQueue = new LinkedBlockingQueue<>();//基於鏈表的先進先出隊列,無界
    workQueue = new SynchronousQueue<>();//無緩衝的等待隊列,無界html

  • 四種拒絕策略:

    RejectedExecutionHandler rejected = null;
    rejected = new ThreadPoolExecutor.AbortPolicy();//默認,隊列滿了丟任務拋出異常
    rejected = new ThreadPoolExecutor.DiscardPolicy();//隊列滿了丟任務不異常
    rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//將最先進入隊列的任務刪,以後再嘗試加入隊列
    rejected = new ThreadPoolExecutor.CallerRunsPolicy();//若是添加到線程池失敗,那麼主線程會本身去執行該任務java

  • 五種線程池:

    ExecutorService threadPool = null;
    threadPool = Executors.newCachedThreadPool();//有緩衝的線程池,線程數 JVM 控制
    threadPool = Executors.newFixedThreadPool(3);//固定大小的線程池
    threadPool = Executors.newScheduledThreadPool(2);
    threadPool = Executors.newSingleThreadExecutor();//單線程的線程池,只有一個線程在工做
    threadPool = new ThreadPoolExecutor();//默認線程池,可控制參數比較多數組

1. 什麼是線程池

很簡單,簡單看名字就知道是裝有線程的池子,咱們能夠把要執行的多線程交給線程池來處理,和鏈接池的概念同樣,經過維護必定數量的線程池來達到多個線程的複用。安全

2. 線程池的好處

咱們知道不用線程池的話,每一個線程都要經過new Thread(xxRunnable).start()的方式來建立並運行一個線程,線程少的話這不會是問題,而真實環境可能會開啓多個線程讓系統和程序達到最佳效率,當線程數達到必定數量就會耗盡系統的CPU和內存資源,也會形成GC頻繁收集和停頓,由於每次建立和銷燬一個線程都是要消耗系統資源的,若是爲每一個任務都建立線程這無疑是一個很大的性能瓶頸。因此,線程池中的線程複用極大節省了系統資源,當線程一段時間再也不有任務處理時它也會自動銷燬,而不會長駐內存。多線程

3. 線程池核心類-- 參數流程詳解

在java.util.concurrent包中咱們能找到線程池的定義,其中ThreadPoolExecutor是咱們線程池核心類,首先看看線程池類的主要參數有哪些。併發

/**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters and default thread factory.
 */
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;
}
  • corePoolSize:線程池的核心大小,也能夠理解爲最小的線程池大小。
  • maximumPoolSize:最大線程池大小。
  • keepAliveTime:空餘線程存活時間,指的是超過corePoolSize的空餘線程達到多長時間才進行銷燬。
  • unit:銷燬時間單位。
  • workQueue:存儲等待執行線程的工做隊列。
  • threadFactory:建立線程的工廠,通常用默認便可。
  • handler:拒絕策略,當工做隊列、線程池全已滿時如何拒絕新任務,默認拋出異常。

4. 線程池工做流程

  • 一、若是線程池中的線程小於corePoolSize時就會建立新線程直接執行任務。
  • 二、若是線程池中的線程大於corePoolSize時就會暫時把任務存儲到工做隊列workQueue中等待執行。
  • 三、若是工做隊列workQueue也滿時,當線程數小於最大線程池數maximumPoolSize時就會建立新線程來處理,而線程數大於等於最大線程池數maximumPoolSize時就會執行拒絕策略。

5. 線程池分類

Executors是jdk裏面提供的建立線程池的工廠類,它默認提供了4種經常使用的線程池應用,而沒必要咱們去重複構造。async

  • newFixedThreadPool
    固定線程池,核心線程數和最大線程數固定相等,而空閒存活時間爲0毫秒,說明此參數也無心義,工做隊列爲最大爲Integer.MAX_VALUE大小的阻塞隊列。當執行任務時,若是線程都很忙,就會丟到工做隊列等有空閒線程時再執行,隊列滿就執行默認的拒絕策略。
/**
  * Creates a thread pool that reuses a fixed number of threads
  * operating off a shared unbounded queue, using the provided
  * ThreadFactory to create new threads when needed. 
  */
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
}
  • newCachedThreadPool
    帶緩衝線程池,從構造看核心線程數爲0,最大線程數爲Integer最大值大小,超過0個的空閒線程在60秒後銷燬,SynchronousQueue這是一個直接提交的隊列,意味着每一個新任務都會有線程來執行,若是線程池有可用線程則執行任務,沒有的話就建立一個來執行,線程池中的線程數不肯定,通常建議執行速度較快較小的線程,否則這個最大線程池邊界過大容易形成內存溢出。
/**
  * Creates a thread pool that creates new threads as needed, but
  * will reuse previously constructed threads when they are
  * available.  These pools will typically improve the performance
  * of programs that execute many short-lived asynchronous tasks.
  */
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
  • newSingleThreadExecutor
    單線程線程池,核心線程數和最大線程數均爲1,空閒線程存活0毫秒一樣無心思,意味着每次只執行一個線程,多餘的先存儲到工做隊列,一個一個執行,保證了線程的順序執行。
/**
 * Creates an Executor that uses a single worker thread operating
 * off an unbounded queue. (Note however that if this single
 * thread terminates due to a failure during execution prior to
 * shutdown, a new one will take its place if needed to execute
 * subsequent tasks.)  Tasks are guaranteed to execute
 * sequentially, and no more than one task will be active at any
 * given time. Unlike the otherwise equivalent
 */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  • newScheduledThreadPool
    調度線程池,即按必定的週期執行任務,即定時任務,對ThreadPoolExecutor進行了包裝而已。
/**
 * Creates a thread pool that can schedule commands to run after a
 * given delay, or to execute periodically.
 */
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

6. 拒絕策略

  • AbortPolicy
    簡單粗暴,直接拋出拒絕異常,這也是默認的拒絕策略。
  • CallerRunsPolicy
    若是線程池未關閉,則會在調用者線程中直接執行新任務,這會致使主線程提交線程性能變慢。
  • DiscardPolicy
    從方法看沒作任務操做,即表示不處理新任務,即丟棄。
  • DiscardOldestPolicy
    拋棄最老的任務,就是從隊列取出最老的任務而後放入新的任務進行執行。

7.緩衝隊列BlockingQueue

  1. 緩衝隊列BlockingQueue簡介:ide

    BlockingQueue是雙緩衝隊列。BlockingQueue內部使用兩條隊列,容許兩個線程同時向隊列一個存儲,一個取出操做。在保證併發安全的同時,提升了隊列的存取效率。
  2. 經常使用的幾種BlockingQueue:
  • ArrayBlockingQueue(int i):規定大小的BlockingQueue,其構造必須指定大小。其所含的對象是FIFO順序排序的。
  • LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其構造時指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE來決定。其所含的對象是FIFO順序排序的。
  • PriorityBlockingQueue()或者(int i):相似於LinkedBlockingQueue,可是其所含對象的排序不是FIFO,而是依據對象的天然順序或者構造函數的Comparator決定。
  • SynchronizedQueue():特殊的BlockingQueue,對其的操做必須是放和取交替完成。

8. 如何提交線程

能夠先隨便定義一個固定大小的線程池函數

ExecutorService es = Executors.newFixedThreadPool(3);

提交一個線程性能

es.submit(xxRunnble);
es.execute(xxRunnble);

submit和execute分別有什麼區別呢?

    1. execute沒有返回值,若是不須要知道線程的結果就使用execute方法,性能會好不少。
    1. submit返回一個Future對象,若是想知道線程結果就使用submit提交,並且它能在主線程中經過Future的get方法捕獲線程中的異常。

咱們來看看execute()到底方法是如何處理的:

    1. 獲取當前線程池的狀態。
    1. 當前線程數量小於coreSize時建立一個新的線程運行。
    1. 若是當前線程處於運行狀態,而且寫入阻塞隊列成功。
    1. 雙重檢查,再次獲取線程狀態;若是線程狀態變了(非運行狀態)就須要從阻塞隊列移除任務,並嘗試判斷線程是否所有執行完畢,同時執行拒絕策略。
    1. 若是當前線程池爲空就新建立一個線程並執行。
    1. 若是在第三步的判斷爲非運行狀態,嘗試新建線程,若是失敗則執行拒絕策略。

9. 如何關閉線程池

es.shutdown();

再也不接受新的任務,以前提交的任務等執行結束再關閉線程池。

es.shutdownNow();

再也不接受新的任務,試圖中止池中的任務再關閉線程池,返回全部未處理的線程list列表。

10. 總結

線程池主要用來解決線程生命週期開銷問題和資源不足問題。經過對多個任務重複使用線程,線程建立的開銷就被分攤到多個任務上,並且因爲在請求到達時線程已經存在,因此消除線程建立所帶來的延遲。這樣,就能夠當即爲請求服務,使應用程序響應更快。另外,經過適當的調整線程中的線程數目能夠防止出現資源不足。

相關文章
相關標籤/搜索