1. 程式人生 > >線程池總結

線程池總結

rtp shu repo 阻塞隊列 對象 util rec 策略 本地

一、為什麽使用線程池

  1)提高性能:系統啟動一個新線程的成本是比較高的,而使用線程池避免了頻繁地創建和銷毀線程,可以很好地提高性能。線程池裏的線程結束後並不會死亡,而是回到線程池中稱為空閑線程,等待使用;

  2)控制線程數量:使用線程池還可以有效地控制系統中並發線程的數量,當系統中包含大量並發線程時,會導致系統性能劇烈下降,甚至導致JVM崩潰,而線程池的最大線程數參數可以控制系統中並發線程數量。

二、一個使用線程池的簡單案例

  步驟:
  1)調用Executors類的靜態方法創建一個ExecutorService對象,該對象代表一個線程池;
  2)創建Runnable實現類或Callable實現類的實例,作為線程執行任務;

  3)調用ExecutorService對象的submit()方法來提交Runnable實例或Callable實例;
  4)當不想提交任何任務時,調用ExecutorService對象的shutdown()方法來關閉線程池;

public class ThreadPoolTest{
    public static void main(String[] args){
        //1.創建線程池
        ExecutorService pool = Executors.newFixedThreadPool(6);
        //2.創建要執行的線程任務
        MyThread mt1 = new
MyThread(); MyThread mt2 = new MyThread(); //3.提交線程任務 pool.submit(mt1); pool.submit(mt2); //4.關閉線程池 pool.shutdown(); } } class MyThread implements Runnable{ public void run(){ for(int i = 0; i < 100; i++){ System.out.println(
"MyThread--" + "i"); } } }

三、深入了解線程池的參數

  ThreadPoolExecutor是java線程池框架(Executor-> ExecutorService)的主要實現類,它的構造函數參數如下:

  public ThreadPoolExecutor( int corePoolSize, //核心線程的數量
               int maximumPoolSize, //最大線程數量
        long keepAliveTime, //超出核心線程數量以外的線程空余存活時間
        TimeUnit unit, //存活時間的單位
        BlockingQueue<Runnable> workQueue, //保存待執行任務的隊列
        ThreadFactory threadFactory, //創建新線程使用的工廠
        RejectedExecutionHandler handler // 當任務無法執行時的處理器 )

  對線程池的參數的介紹:

  1)corePoolSize:核心線程池數量 。在線程數少於核心數量時,有新任務進來就新建一個線程,即使有的線程沒事幹;等超出核心數量後,就不會新建線程了,空閑的線程就得去任務隊列裏取任務執行了。
  2)maximumPoolSize:最大線程數量 ,包括核心線程池數量 + 核心以外的數量。
    如果任務隊列滿了,並且池中線程數小於最大線程數,會再創建新的線程執行任務。
  3)keepAliveTime:核心池以外的線程存活時間,即沒有任務的外包的存活時間
    如果給線程池設置 allowCoreThreadTimeOut(true),則核心線程在空閑時頭上也會響起死亡的倒計時
    如果任務是多而容易執行的,可以調大這個參數,那樣線程就可以在存活的時間裏有更大可能接受新任務
  4)workQueue:保存待執行任務的阻塞隊列 。線程池中使用的隊列是 BlockingQueue 接口,常用的實現有如下幾種:

    a)ArrayBlockingQueue:基於數組、有界,按 FIFO(先進先出)原則對元素進行排序;

    b)LinkedBlockingQueue:基於鏈表,按FIFO (先進先出) 排序元素;

      - 吞吐量通常要高於 ArrayBlockingQueue

      - Executors.newFixedThreadPool() 使用了這個隊列

    c)SynchronousQueue:不存儲元素的阻塞隊列;

      - 每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態

      - 吞吐量通常要高於 LinkedBlockingQueue

      - Executors.newCachedThreadPool使用了這個隊列

    d)PriorityBlockingQueue:具有優先級的、無限阻塞隊列。


  5)threadFactory:每個線程創建的地方。可以給線程起個好聽的名字,設置個優先級等。
  6)handler:飽和策略,大家都很忙,咋辦呢,有四種策略 。
    CallerRunsPolicy:只要線程池沒關閉,就直接用調用者所在線程來運行任務
    AbortPolicy:直接拋出 RejectedExecutionException 異常
    DiscardPolicy:悄悄把任務放生,不做了
    DiscardOldestPolicy:把隊列裏待最久的那個任務扔了,然後再調用 execute() 試試看能行不

四、線程池的處理流程

  線程池具體的執行方法ThreadPoolExecutor.execute:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    //1.當前池中線程比核心數少,新建一個線程執行任務
    if (workerCountOf(c) < corePoolSize) {   
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2.核心池已滿,但任務隊列未滿,添加到隊列中
    if (isRunning(c) && workQueue.offer(command)) {   
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))    //如果這時被關閉了,拒絕任務
            reject(command);
        else if (workerCountOf(recheck) == 0)    //如果之前的線程已被銷毀完,新建一個線程
            addWorker(null, false);
    }
    //3.核心池已滿,隊列已滿,試著創建一個新線程
    else if (!addWorker(command, false))
        reject(command);    //如果創建新線程失敗了,說明線程池被關閉或者線程池完全滿了,拒絕任務
}

  線程池的處理流程圖:

技術分享圖片

五、JDK 提供的線程池及使用場景

  1.java.util.concurrent.Executors是JDK自帶的創建線程的工具類,其創建的線程池是ThreadPoolExecutor的實例。Executors創建線程池比較典型的4種方式:

  1) newFixedThreadPool:創建一個核心線程個數和最大線程個數都為nThreads的線程池,並且阻塞隊列長度為Integer.MAX_VALUE,keeyAliveTime=0說明只要線程個數比核心線程個數多並且當前空閑則回收。可能引發的問題:阻塞隊列太大會引起OOM;核心線程會常駐內存,可能會造成資源浪費。

  2)newSingleThreadExecutor:創建一個核心線程個數和最大線程個數都為1的線程池,並且阻塞隊列長度為Integer.MAX_VALUE,keeyAliveTime=0說明只要線程個數比核心線程個數多並且當前空閑則回收。缺點:單線程,可能會堆積大量待執行的任務。

  3)newCachedThreadPool: 創建一個按需創建線程的線程池,最大線程數為Integer.MAX_VALUE,阻塞隊列為最多只有一個任務的同步隊列,keeyAliveTime=60只要線程60s內空閑則回收。問題點:最大線程數太大會引起OOM。

  4)newScheduledThreadPool:創建一個最小線程個數corePoolSize,最大為Integer.MAX_VALUE,阻塞隊列為DelayedWorkQueue的線程池。

  

  2.如何選擇使用哪個JDK 提供的線程池?

    - CachedThreadPool 用於並發執行大量短期的小任務,或者是負載較輕的服務器。
    - FixedThreadPool 用於負載比較重的服務器,為了資源的合理利用,需要限制當前線程數量。
    - SingleThreadExecutor 用於串行執行任務的場景,每個任務必須按順序執行,不需要並發執行。
    - ScheduledThreadPoolExecutor 用於需要多個後臺線程執行周期任務,同時需要限制線程數量的場景。

  3.註意:Executors創建線程的方式雖然簡單方便,但並不推薦使用此種方式。建議使用new ThreadPoolExecutor(...)的方式創建線程池。

六、兩種提交任務的方法

  ExecutorService 提供了兩種提交任務的方法:
    execute():提交不需要返回值的任務
    submit():提交需要返回值的任務

七、線程池使用註意事項

  1)建議使用new ThreadPoolExecutor(...)的方式創建線程池:

  線程池的創建不應使用 Executors 去創建,而應該通過 ThreadPoolExecutor 創建,這樣可以讓讀者更加明確地知道線程池的參數設置、運行規則,規避資源耗盡的風險,這一點在也阿裏巴巴JAVA開發手冊中也有明確要求。

  2)合理設置線程數:

  線程池的工作線程數設置應根據實際情況配置,CPU密集型業務(搜索、排序等)CPU空閑時間較少,線程數不能設置太多。N核服務器,通過執行業務的單線程分析出本地計算時間為x,等待時間為y,則工作線程數(線程池線程數)設置為 N*(x+y)/x,能讓CPU的利用率最大化。

  3)設置能代表具體業務的線程名稱:這樣方便通過日誌的線程名稱識別所屬業務。具體實現可以通過指定ThreadPoolExecutor的ThreadFactory參數。如使用spring提供的CustomizableThreadFactory。

參考文獻:

  1)https://www.cnblogs.com/gdpdroid/p/4128177.html

  2)https://blog.csdn.net/u011240877/article/details/73440993

  3)https://baijiahao.baidu.com/s?id=1595521551376533549&wfr=spider&for=pc

線程池總結