- 近年來由于互聯(lián)網(wǎng)的興起,所以現(xiàn)在Java 面試題中也經(jīng)常會(huì)問到線程池技術(shù),所以今天我們就說一說面試中經(jīng)常問道的知識(shí)點(diǎn)。
一 、 基礎(chǔ)知識(shí)
- 為什么要使用線程池呢?
- 在實(shí)際使用中,線程是很占用系統(tǒng)資源的,如果對(duì)線程管理不善很容易導(dǎo)致系統(tǒng)問題。因此,在大多數(shù)并發(fā)框架中都會(huì)使用線程池技術(shù)來管理線程,那么使用線程池管理線程主要有下面三點(diǎn)好處:
- 降低資源消耗。 通過復(fù)用已經(jīng)存在的線程和降低線程關(guān)閉的次數(shù)來盡可能降低系統(tǒng)的消耗;
- 提升系統(tǒng)響應(yīng)速度。通過復(fù)用線程,省去創(chuàng)建線程的過程,因此整體上提升了系統(tǒng)的響應(yīng)速度。
- 提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)的資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,因此,需要使用線程池管理線程。
- 在實(shí)際使用中,線程是很占用系統(tǒng)資源的,如果對(duì)線程管理不善很容易導(dǎo)致系統(tǒng)問題。因此,在大多數(shù)并發(fā)框架中都會(huì)使用線程池技術(shù)來管理線程,那么使用線程池管理線程主要有下面三點(diǎn)好處:
- Java 線程的整體架構(gòu)體系
-
Java中的線程池是通過Executor框架實(shí)現(xiàn)的,該框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor這幾個(gè)類 。
image.png - 三個(gè)JDK 線程實(shí)現(xiàn)類
- Executors.newFixedThreadPool(int):執(zhí)行長期任務(wù)性能好,創(chuàng)建一個(gè)線程池,一個(gè)池中有N個(gè)固定線程,有固定線程數(shù)的線程池。
-
// 代碼實(shí)現(xiàn) 模擬十個(gè)人去銀行辦理業(yè)務(wù),然后有5個(gè)窗口進(jìn)行業(yè)務(wù)
ExecutorService executorService = Executors.newFixedThreadPool(5); //固定線程數(shù)
for (int i = 0; i < 10; i++) {
// Thread.sleep(20);
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+" 辦理業(yè)務(wù)");
});
}
executorService.shutdown();

image.png
- Executors.newSingleThreadExecutor():一個(gè)一個(gè)任務(wù)的執(zhí)行,一池一線程
// 代碼實(shí)現(xiàn)
ExecutorService executorService = Executors.newSingleThreadExecutor(); //一池子一個(gè)工作線程類似銀行只有一個(gè)窗口
for (int i = 0; i < 10; i++) {
// Thread.sleep(20);
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+" 辦理業(yè)務(wù)");
});
}
executorService.shutdown();

image.png
- Executors.newCachedThreadPool(): 執(zhí)行很
多短期任務(wù),線程池根據(jù)需要?jiǎng)?chuàng)建新線程,但是先前構(gòu)建的線程可用時(shí)將他重用起來,可擴(kuò)容。
// 代碼實(shí)現(xiàn)
ExecutorService executorService = Executors.newCachedThreadPool(); //可擴(kuò)容線程池
for (int i = 0; i < 10; i++) {
// Thread.sleep(20);
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+" 辦理業(yè)務(wù)");
});
}
executorService.shutdown();

image.png
-
以上三個(gè)實(shí)現(xiàn)方法是Java 給我們帶的,但是如果在面試的時(shí)候面試官問你線程池的技術(shù)你要是管說這三個(gè)實(shí)現(xiàn),基本上你就掛了。其實(shí)我們看他們?nèi)齻€(gè)的底層實(shí)現(xiàn)都是一個(gè)類那就是ThreadPoolExecutor
image.png
image.png
image.png
- ThreadPoolExecutor
-
那么我們看看那個(gè)ThreadPoolExecutor 類里面的內(nèi)容,下面就是線程池的源碼,那我們現(xiàn)在就看看這些源碼。
image.png - 我們先看一下線程池的主要參數(shù),7個(gè)參數(shù)
- corePoolSize :線程池中常駐核心線程數(shù)
- maximumPoolSize:線程池中能夠容納同時(shí)執(zhí)行最大的線程數(shù),此值必須大于等于1
- keepAliveTime: 多余的空閑線程的存貨時(shí)間,達(dá)到keepAliveTime時(shí),多余線程會(huì)被銷毀直到剩下corePoolSize個(gè)線程數(shù)為止。
- unit:keepAliveTime 的單位
- workQueue:任務(wù)隊(duì)列,被提交但未被執(zhí)行的任務(wù)。
- threadFactory:表示生成線程池中工作線程的線程工廠,用于創(chuàng)建線程,一般默認(rèn)即可。
- handler:拒絕策略,表示當(dāng)隊(duì)列滿了,并且工作線程大于等于線程池中最大的線程數(shù)(maximumPoolSize)時(shí)如何拒絕執(zhí)行的runable的策略。
-
以上7個(gè)參數(shù)我們都是必須要掌握的,而且要知道為什么這么設(shè)置,所以接下來我們我們要看一下線程池的底層原理。
image.png - 線程池的底層工作原理(以下很重要,包括上面的圖也是很重要的)
- 在創(chuàng)建線程池后,開始等待請(qǐng)求。
- 在調(diào)用execute()方法添加一個(gè)請(qǐng)求任務(wù)時(shí),線程池會(huì)做出如下判斷:
2.1 如果正在運(yùn)行的線程數(shù)量小于corePoolSize,那么馬上會(huì)創(chuàng)建線程運(yùn)行任務(wù);
2.2 如果正在運(yùn)行的線程數(shù)量大于或等于corePoolSize,那么將這個(gè)任務(wù)放入隊(duì)列;
2.3 如果這個(gè)時(shí)候隊(duì)列滿了且正在運(yùn)行的線程數(shù)量還小于maximumPoolSize,那么還是要?jiǎng)?chuàng)建非線程立刻運(yùn)行這個(gè)任務(wù);
2.4 如果隊(duì)列滿了且正在運(yùn)行的線程數(shù)量大于或者等于maximumPoolSize,那么線程池會(huì)啟動(dòng)飽和拒絕策略來執(zhí)行。 - 當(dāng)一個(gè)線程完成任務(wù)時(shí),它會(huì)從隊(duì)列中取下一個(gè)任務(wù)來執(zhí)行。
- 當(dāng)一個(gè)線程無事可做超過一定的時(shí)間(keepAliveTime)時(shí),線程會(huì)判斷:如果當(dāng)前運(yùn)行的線程數(shù)量大于corePoolSize,那么這個(gè)線程就會(huì)被停掉。所以線程池的所有任務(wù)完成后,它最終會(huì)收縮到corePoolSize的大小。
-
以上時(shí)線程池執(zhí)行的整體過程,下面要看一下線程的處理過程。
image.png
- 線程池用哪個(gè)?生產(chǎn)中如設(shè)置合理參數(shù)呢 ?
- 線程池的拒絕策略 。
- 什么是線程池的拒絕策略呢:等待隊(duì)列已經(jīng)排滿了,再也塞不下新任務(wù)了同時(shí),線程池中的max線程也達(dá)到了,無法繼續(xù)為新任務(wù)服務(wù)。這個(gè)是時(shí)候我們就需要拒絕策略機(jī)制合理的處理這個(gè)問題。
- JDK內(nèi)置的4個(gè)拒絕策略(以下內(nèi)置拒絕策略均實(shí)現(xiàn)了
RejectedExecutionHandle接口)- AbortPolicy(默認(rèn)):直接拋出RejectedExecutionException異常阻止系統(tǒng)正常運(yùn)行
- CallerRunsPolicy:“調(diào)用者運(yùn)行”一種調(diào)節(jié)機(jī)制,該策略既不會(huì)拋棄任務(wù),也不
會(huì)拋出異常,而是將某些任務(wù)回退到調(diào)用者,從而降低新任務(wù)的流量。 - DiscardOldestPolicy:拋棄隊(duì)列中等待最久的任務(wù),然后把當(dāng)前任務(wù)加人隊(duì)列中嘗試再次提交當(dāng)前任務(wù)。
- DiscardPolicy:該策略默默地丟棄無法處理的任務(wù),不予任何處理也不拋出異常。如果允許任務(wù)丟失,這是最好的一種策略。
- 工作中我們選擇什么線程池呢? 這個(gè)是面試常問的考點(diǎn),如果你要是回答JDK默認(rèn)的三種線程池技術(shù)你就完蛋了,代表你只是了解一些,根本沒有用過。
- 答案是一個(gè)都不用,工作中只能用自定義的。
- JDK 都提供了為什么不用呢?
- 這就跟源碼有關(guān)系了,我們看看源碼。
//Executors.newFixedThreadPool() 源碼 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }// Executors.newSingleThreadExecutor() public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }//Executors.newCachedThreadPool() public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }- 看看JDK給我們自帶的實(shí)現(xiàn),之前我們說過7大參數(shù),那么現(xiàn)在看看5個(gè)參數(shù) 其中這些參數(shù)設(shè)計(jì)的及其不合理(newCachedThreadPool 中的maximumPoolSize設(shè)置的是Integer.MAX_VALUE,有些太大了,這樣會(huì)導(dǎo)致系統(tǒng)失敗的),根本不符合企業(yè)的要求,所以我們要自定義。- 自定義線程池(代碼要會(huì)手寫)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 2, 5, 3L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy()); for (int i = 0; i < 9; i++) { //Thread.sleep(20); threadPoolExecutor.execute(()->{ System.out.println(Thread.currentThread().getName()+" 辦理業(yè)務(wù)"); }); } threadPoolExecutor.shutdown();- 但是這些參數(shù)我們要怎么設(shè)置呢? 其中最重要的就是配置最大線程數(shù)
- 如果線程池要處理的任務(wù)是cpu密集型,那么最大的任務(wù)就是cpu核數(shù)+ 1(但是我們不能寫死了 要使用代碼自動(dòng)獲取Runtime.getRuntime().availableProcessors())。
- 如果線程池要處理的任務(wù)是IO密集型,那么最大的任務(wù)就是cpu核數(shù)/阻塞系數(shù)。
- 線程池的拒絕策略 。






