線程池總結
一、為什麽使用線程池
1)提高性能:系統啟動一個新線程的成本是比較高的,而使用線程池避免了頻繁地創建和銷毀線程,可以很好地提高性能。線程池裏的線程結束後並不會死亡,而是回到線程池中稱為空閑線程,等待使用;
2)控制線程數量:使用線程池還可以有效地控制系統中並發線程的數量,當系統中包含大量並發線程時,會導致系統性能劇烈下降,甚至導致JVM崩潰,而線程池的最大線程數參數可以控制系統中並發線程數量。
二、一個使用線程池的簡單案例
步驟:
1)調用Executors類的靜態方法創建一個ExecutorService對象,該對象代表一個線程池;
2)創建Runnable實現類或Callable實現類的實例,作為線程執行任務;
4)當不想提交任何任務時,調用ExecutorService對象的shutdown()方法來關閉線程池;
public class ThreadPoolTest{ public static void main(String[] args){ //1.創建線程池 ExecutorService pool = Executors.newFixedThreadPool(6); //2.創建要執行的線程任務 MyThread mt1 = newMyThread(); 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
線程池總結