線程池:指在初始化一個多線程應用程序過程當中建立一個線程集合,而後在須要執行新的任務時重用這些線程而不是新建一個線程,java
一旦任務已經完成了,線程回到池子中並等待下一次分配任務。程序員
1)控制最大並大數。緩存
2)下降資源消耗。經過重複利用已建立的線程來下降線程建立和銷燬形成的消耗。服務器
3)提升響應速度。當任務到達時,任務不須要等到線程建立,而是能夠直接使用線程池中的空閒線程。多線程
4)提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配、延時執行、調優和監控等。ide
經常使用的線程池接口和類都在 java.util.concurrent包下,大體爲:工具
Executor:線程池的頂級接口this
ExecutorService:線程池接口,可經過submit()方法提交任務代碼.net
ExecutorService接口的實現類最經常使用的爲如下兩個:線程
ThreadPoolExecutor
ScheduledThreadPoolExecutor
和 Array -> Arrays、Collection -> Collections 同樣,線程池的建立也是有工具類可使用的:
Executors工廠類:經過此類能夠建立一個線程池
在 JDK 8 之後,一共有 5 種線程池,分別爲:
固定線程數的線程池
只有一個線程的線程池
可根據任務數動態擴容線程數的線程池
可調度的線程池
具備搶佔式操做的線程池
這些線程池都能由 Executors 工具類來進行建立,分別對應如下方法:
1)newFixedThreadPool:建立指定的、固定個數的線程池
2)newCachedThreadPool:建立緩存線程池(線程個數根據任務數逐漸增長,上線爲 Integer.MAX_VALUE)
3)newSingleThreadExecutor:建立單個線程的線程池
4)newScheduledThreadPool:建立可調度的線程池 調度:定時、週期執行5)newWorkStealingPool:建立具備搶佔式操做的線程池
對於 newWorkStealingPool 的補充:
newWorkStealingPool,這個是 JDK1.8 版本加入的一種線程池,stealing 翻譯爲搶斷、竊取的意思,它實現的一個線程池和上面4種都不同,用的是 ForkJoinPool 類。
newWorkStealingPool 適合使用在很耗時的操做,可是 newWorkStealingPool 不是 ThreadPoolExecutor 的擴展,它是新的線程池類 ForkJoinPool 的擴展,可是都是在統一的一個 Executors 類中實現,因爲可以合理的使用 CPU 進行任務操做(並行操做),因此適合使用在很耗時的任務中。
參考文章:
https://blog.csdn.net/qq_38428623/article/details/86689800
https://blog.csdn.net/tjbsl/article/details/98480843
1)建立線程池對象
2)建立線程任務
3)使用線程池對象的 submit() 或者 execute() 方法提交要執行的任務
4)使用完畢,可使用shutdown()方法關閉線程池
需求:使用線程池管理線程來簡單的模擬買票程序。
public class Demo(){ public static void main(String[] args) { test(); } public static void test(){ //一、建立線程池對象 ExecutorService pool = Executors.newFixedThreadPool(4); //二、建立任務 Runnable runnable = new Runnable(){ private int tickets = 100; @Override public void run() { while (true){ if(tickets <= 0){ break; } System.out.println(Thread.currentThread().getName()+"賣了第"+tickets+"張票"); tickets--; } } }; //三、將任務提交到線程池(須要幾個線程來執行就提交幾回) for (int i=0; i<5; i++){ pool.submit(runnable); } //四、關閉線程池 pool.shutdown(); }
補充:
shutdown:啓動有序關閉,其中先前提交的任務將被執行,但不會接受任何新任務
shutdownNow:嘗試中止全部正在執行的任務,中止等待任務的處理,並返回正在等待執行的任務列表。
execute() 和 submit() 的區別:
1)參數:execute 只能傳遞 Runnable;submit 既能夠傳遞 Runnable,也能夠傳遞 Callable
2)返回值:execute 沒有返回值;submit 有返回值,能夠獲取 Callable 的返回結果
newFixedThreadPool
newCachedThreadPool
newSingleThreadExecutor
newScheduledThreadPool
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; }
corePoolSize:線程池中的常駐核心線程數
maximumPoolSize:線程池中可以容納同時指向的最大線程數,此值必須大於等於1
keepAliveTime:多餘空閒線程的存活時間
(若線程池中當前線程數超過corePoolSize時,且空閒線程的空閒時間達到keepAliveTime時,多餘空閒線程會被銷燬,直到只剩下corePoolSize個線程爲止)
TimeUnit:keepAliveTime 的時間單位
workQueue:任務隊列,被提交但還沒有被執行的任務
ThreadFactory:線程工廠,用於建立線程,通常用默認的便可
RejectedExecutionHandler:拒絕策略,當任務太多來不及處理,如何拒絕任務
1)線程池剛建立時,裏面沒有一個線程。任務隊列是做爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會立刻執行它們。
2)當調用 execute() 方法添加一個任務時,線程池會作以下判斷:
a) 若是正在運行的線程數量小於 corePoolSize,那麼立刻建立線程運行這個任務;
b) 若是正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列;
c) 若是這時候隊列滿了,並且正在運行的線程數量小於 maximumPoolSize,那麼仍是要建立非核心線程馬上運行這個任務;
d) 若是隊列滿了,並且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會執行拒絕策略。
3)當一個線程完成任務時,它會從隊列中取下一個任務來執行。
4)當一個線程無事可作,超過必定的時間(keepAliveTime)時,線程池會判斷,若是當前運行的線程數大於 corePoolSize,那麼這個線程就被停掉。因此線程池的全部任務完成後,它最終會收縮到 corePoolSize 的大小。
線程池中的線程已經用完了,沒法繼續爲新任務服務,同時,等待隊列也已經排滿了,再也塞不下新任務了。這時候咱們就須要拒絕策略機制合理的處理這個問題。
JDK 內置的拒絕策略以下:
1)AbortPolicy : 直接拋出 RejectedExecutionException 異常,阻止系統正常運行。
2)CallerRunsPolicy : 該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用線程。
3)DiscardOldestPolicy : 丟棄隊列中等待最久的線程,而後把當前任務加入隊列中嘗試再次提交當前任務。
4)DiscardPolicy : 該策略默默地丟棄沒法處理的任務,不予任何處理。若是容許任務丟失,這是最好的一種方案。
以上內置拒絕策略均實現了 RejectedExecutionHandler 接口,若以上策略仍沒法知足實際須要,徹底能夠本身擴展 RejectedExecutionHandler 接口。
經過查看 Executors 提供的默認線程池的底層源碼後,咱們會發現其有以下弊端:
1)FixedThreadPool 和 SingleThreadPool:
容許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而致使 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
容許的建立線程數量爲 Integer.MAX_VALUE,可能會堆積大量的線程,從而致使 OOM。
而且在《阿里巴巴Java開發手冊》中也有指出,線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式手動建立,這樣的處理方式能讓程序員更加明確線程池的容許規則,從而規避資源耗盡的風險。
小結:在實際開發中不會使用 Executors 建立,而是手動建立,本身指定參數。
以上的參數是隨手寫的,實際開發中參數的設置要根據業務場景以及服務器配置來進行設置。
設置線程池的參數時,須要從如下 2 個方面進行考慮:
系統是 CPU 密集型?
系統是 IO 密集型?
CPU 密集的意思是該任務須要大量的運算,而沒有阻塞,CPU一直全速運行。
那麼這種狀況下,應該儘量配置少的線程數量,從而減小線程之間的切換,讓其充分利用時間進行計算。
通常公式爲:CPU核數 + 1 個線程的線程池。
能夠經過如下代碼來查看服務器的核數:
Runtime.getRuntime().availableProcessors()
IO 密集型的意思是該任務須要大量的 IO,即大量的阻塞。
那麼這種狀況下會致使有大量的 CPU 算力浪費在等待上,因此須要多配置線程數。
在 IO 密集型狀況下,瞭解到有兩種配置線程數的公式:
公式一:CPU核數/(1-阻塞係數),其中阻塞係數在 0.8-0.9 之間
如:8核CPU,能夠設置爲 8/(1-0.9)=80 個線程
公式二:CPU核數 * 2
線程數的設置參考文章:
Java新手,如有錯誤,歡迎指正!