面試官問:你對(duì)線程池了解多少?
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
1. Callable接口的使用
package com.yuxue.juc.threadPool;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 多線程中,第三種獲得多線程的方式
* */
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//FutureTask(Callable<V> callable)
FutureTask<Integer> futureTask = new FutureTask<>(new myThread());
new Thread(futureTask, "AAA").start();
//new Thread(futureTask, "BBB").start();//復(fù)用,直接取值,不要重啟兩個(gè)線程
int a = 100;
int b = 0;
//b = futureTask.get();//要求獲得Callable線程的計(jì)算結(jié)果,如果沒有計(jì)算完成就要去強(qiáng)求,會(huì)導(dǎo)致堵塞,直到計(jì)算完成
while (!futureTask.isDone()) {
////當(dāng)futureTask完成后取值
b = futureTask.get();
}
System.out.println("===Result is:" + (a + b));
}
}
class myThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\tget in the callable");
Thread.sleep(5000);
return 1024;
}
}
兩者區(qū)別:
Callable:有返回值,拋異常
Runnable:無返回值,不拋出異常
2. 為什么要使用線程池
線程池做的工作主要是控制運(yùn)行的線程的數(shù)量,處理過程中將任務(wù)放入隊(duì)列,然后在線程創(chuàng)建后啟動(dòng)給這些任務(wù),如果線程數(shù)量超過了最大數(shù)量,超出數(shù)量的線程排隊(duì)等候,等其他線程執(zhí)行完畢,再從隊(duì)列中取出任務(wù)來執(zhí)行
主要特點(diǎn)
線程復(fù)用、控制最大并發(fā)數(shù)、管理線程
減少創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開銷 => 減少內(nèi)存開銷,創(chuàng)建線程占用內(nèi)存,創(chuàng)建線程需要時(shí)間,會(huì)延遲處理的請(qǐng)求;降低資源消耗,通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗
提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行
提高線程的客觀理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控(根據(jù)系統(tǒng)承受能力,達(dá)到運(yùn)行的最佳效果) => 避免無限創(chuàng)建線程引起的
OutOfMemoryError【簡(jiǎn)稱OOM】
3. 線程池如何使用?
架構(gòu)說明
Java中的線程池是通過Executor框架實(shí)現(xiàn)的,該框架中用到了 :
Executor,Executors,ExecutorService,ThreadPoolExecutor
編碼實(shí)現(xiàn)
實(shí)現(xiàn)有五種,
Executors.newScheduledThreadPool()是帶時(shí)間調(diào)度的,java8新推出Executors.newWorkStealingPool(int),使用目前機(jī)器上可用的處理器作為他的并行級(jí)別重點(diǎn)有三種
我們可以看到底層的代碼都是由ThreadPoolExecutor這個(gè)類的構(gòu)造方法創(chuàng)建的,只是傳入的參數(shù)不同,那么研究一下這個(gè)類以及這些參數(shù)就很有必要,下節(jié)我們將介紹這些參數(shù)的使用
Executors.newFixedThreadPool(int)執(zhí)行長(zhǎng)期的任務(wù),性能好很多
創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程回在隊(duì)列中等待newFixedThreadPool創(chuàng)建的線程池corePoolSize和maximumPoolSize值是相等的,它使用的是 LinkedBlockingQueue
底層源碼:

Executors.newSingleThreadExecutor()一個(gè)任務(wù)一個(gè)任務(wù)執(zhí)行的場(chǎng)景
創(chuàng)建一個(gè)單線程話的線程池,他只會(huì)用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序執(zhí)行
newSingleThreadExecutor將corePoolSize和maximumPoolSize都設(shè)置為1,使用LinkedBlockingQueue
源碼:

Executors.newCachedThreadPool()執(zhí)行很多短期異步的小程序或負(fù)載較輕的服務(wù)器
創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑縣城,若無可回收,則新建線程
newCachedThreadPool將corePoolSize設(shè)置為0,將maximumPoolSize設(shè)置為Integer.MAX_VALUE,使用 的SynchronousQueue,也就是說來了任務(wù)就創(chuàng)建線程運(yùn)行,當(dāng)縣城空閑超過60s,就銷毀線程
源碼:

ThreadPoolExecutor
4. 線程池的幾個(gè)重要參數(shù)
核心執(zhí)行代碼為:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}那么我們?cè)冱c(diǎn)進(jìn)this方法可以看到其全參數(shù)的構(gòu)造方法為:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
簡(jiǎn)單介紹一下:
corePoolSize: 線程池中常駐核心線程數(shù)
在創(chuàng)建了線程池后,當(dāng)有請(qǐng)求任務(wù)來之后,就會(huì)安排池中的線程去執(zhí)行請(qǐng)求任務(wù)
當(dāng)線程池的線程數(shù)達(dá)到corePoolSize后,就會(huì)把到達(dá)的任務(wù)放到緩存隊(duì)列當(dāng)中
maximumPoolSize: 線程池能夠容納同時(shí)執(zhí)行的最大線程數(shù),必須大于等于1
keepAliveTime: 多余的空閑線程的存活時(shí)間
當(dāng)前線程池?cái)?shù)量超過corePoolSize時(shí),檔口空閑時(shí)間達(dá)到keepAliveTime值時(shí),多余空閑線程會(huì)被銷毀到只剩下corePoolSize個(gè)線程為止
unit: keepAliveTime的單位
workQueue: 任務(wù)隊(duì)列,被提交但尚未被執(zhí)行的任務(wù)
任務(wù)隊(duì)列底層是BlockingQueue阻塞隊(duì)列!不清楚阻塞隊(duì)列的參考這篇文章:用阻塞隊(duì)列實(shí)現(xiàn)一個(gè)生產(chǎn)者消費(fèi)者模型?
threadFactory:表示生成線程池中工作線程的線程工廠,用于創(chuàng)建線程一般用默認(rèn)的即可
handler: 拒絕策略,表示當(dāng)隊(duì)列滿了并且工作線程大于等于線程池的最大線程數(shù)(maximumPoolSize)時(shí)如 何來拒絕請(qǐng)求執(zhí)行的runable的策略
5. 線程池的底層工作原理以及過程

如上圖所屬,其流程為:
在創(chuàng)建了線程池之后,等待提交過來的任務(wù)請(qǐng)求
當(dāng)調(diào)用execute()方法添加一個(gè)請(qǐng)求任務(wù)時(shí),線程池會(huì)做出如下判斷:
2.1 如果正在運(yùn)行的線程數(shù)量小于corePoolSize,那么馬上創(chuàng)建線程運(yùn)行這個(gè)任務(wù);
2.2 如果正在運(yùn)行的線程數(shù)量大于或等于corePoolSize,那么將這個(gè)任務(wù)放入隊(duì)列;
2.3 如果此時(shí)隊(duì)列滿了且運(yùn)行的線程數(shù)小于maximumPoolSize,那么還是要?jiǎng)?chuàng)建非核心線程立刻運(yùn)行此任務(wù);
2.4 如果隊(duì)列滿了且正在運(yùn)行的線程數(shù)量大于或等于maxmumPoolSize,那么啟動(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的大小
5. 實(shí)際工作中如何設(shè)置合理參數(shù)
5.1 線程池的拒絕策略
什么是線程的拒絕策略
等待隊(duì)列也已經(jīng)排滿了,再也塞不下新任務(wù)了,同時(shí)線程池中的max線程也達(dá)到了,無法繼續(xù)為新任務(wù)服務(wù)。這時(shí)我們就需要拒絕策略機(jī)制合理的處理這個(gè)問題
JDK內(nèi)置的拒絕策略
AbortPolicy(默認(rèn)):如果滿了會(huì)直接拋出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ù)丟失,這是最好的一種方案
均實(shí)現(xiàn)了RejectedExecutionHandler接口
5.2 你在工作中單一的/固定數(shù)的/可變的三種創(chuàng)建線程池的方法,用哪個(gè)多?
回答:一個(gè)都不用,我們生產(chǎn)上只能使用自定義的!!!!
為什么?
線程池不允許使用Executors創(chuàng)建,試試通過ThreadPoolExecutor的方式,規(guī)避資源耗盡風(fēng)險(xiǎn)
阿里巴巴規(guī)范手冊(cè)當(dāng)中提到:
FixedThreadPool和SingleThreadPool允許請(qǐng)求隊(duì)列長(zhǎng)度為Integer.MAX_VALUE,可能會(huì)堆積大量請(qǐng)求,導(dǎo)致OOM;
CachedThreadPool和ScheduledThreadPool允許的創(chuàng)建線程數(shù)量為Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量線程,導(dǎo)致OOM
5.3 你在工作中時(shí)如何使用線程池的,是否自定義過線程池?
上代碼:
package com.yuxue.juc.threadPool;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
//corePoolSize:常駐核心線程數(shù)
2,
//maximumPoolSize:最大的可容納線程數(shù)
5,
//存活時(shí)間設(shè)置為1s
1L,
TimeUnit.SECONDS,
//這里用LinkedBlockingQueue,且容量為3,意味著等候區(qū)最大容量三個(gè)任務(wù)
new LinkedBlockingQueue<>(3),
//默認(rèn)的defaultThreadFactory即可
Executors.defaultThreadFactory(),
//丟棄方法使用AbortPolicy()
new ThreadPoolExecutor.AbortPolicy());
//這里用來做任務(wù)的處理執(zhí)行
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 辦理業(yè)務(wù);");
});
}
threadPoolExecutor.shutdown();
}
}
我們運(yùn)行結(jié)果為: pool-1-thread-2 辦理業(yè)務(wù);
pool-1-thread-1 辦理業(yè)務(wù);
pool-1-thread-2 辦理業(yè)務(wù);
pool-1-thread-1 辦理業(yè)務(wù);
pool-1-thread-2 辦理業(yè)務(wù);
//可以看到當(dāng)我們?nèi)蝿?wù)書為5且處理速度非常快時(shí),我們就用核心的corePoolSize就可以滿足任務(wù)需求
當(dāng)任務(wù)數(shù)量變多或者任務(wù)變重時(shí):如將我們的任務(wù)數(shù)量調(diào)整為20時(shí),此時(shí)運(yùn)行結(jié)果為:
pool-1-thread-1 辦理業(yè)務(wù);
pool-1-thread-3 辦理業(yè)務(wù);
pool-1-thread-2 辦理業(yè)務(wù);
pool-1-thread-3 辦理業(yè)務(wù);
pool-1-thread-1 辦理業(yè)務(wù);
pool-1-thread-4 辦理業(yè)務(wù);
pool-1-thread-1 辦理業(yè)務(wù);
pool-1-thread-3 辦理業(yè)務(wù);
pool-1-thread-2 辦理業(yè)務(wù);
pool-1-thread-5 辦理業(yè)務(wù);
pool-1-thread-4 辦理業(yè)務(wù);
pool-1-thread-1 辦理業(yè)務(wù);
pool-1-thread-3 辦理業(yè)務(wù);
pool-1-thread-1 辦理業(yè)務(wù);
pool-1-thread-2 辦理業(yè)務(wù);
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.yuxue.juc.threadPool.MyThreadPoolDemo$$Lambda$1/558638686@6d03e736 rejected from java.util.concurrent.ThreadPoolExecutor@568db2f2[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 15]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.yuxue.juc.threadPool.MyThreadPoolDemo.main(MyThreadPoolDemo.java:27)
發(fā)生了異常,且任務(wù)只執(zhí)行完了15個(gè),我們可以看到其中active threads = 0, queued tasks = 0也就是說我的阻塞隊(duì)列已經(jīng)滿了,且沒有空閑的線程了,此時(shí)再申請(qǐng)任務(wù)我就會(huì)拋出異常,這是線程池handler參數(shù)的拒絕策略,當(dāng)我們更改策略為ThreadPoolExecutor.CallerRunsPolicy()時(shí),運(yùn)行結(jié)果當(dāng)中存在main 辦理業(yè)務(wù);語句,也就意味著線程池將某些任務(wù)回退到了調(diào)用者,另外的兩個(gè)拒絕策略在此就不演示
5.4 合理配置線程池你是如何考慮的?
CPU密集型:
一般公式:CPU核數(shù)+1個(gè)線程的線程池
CPU密集的意思是該任務(wù)需要大量的運(yùn)算,而沒有阻塞,CPU一直全速運(yùn)行
CPU密集任務(wù)只有在真正多核CPU上才可能得到加速(通過多線程) 而在單核CPU上,無論你開幾個(gè)模擬的多線程該任務(wù)都不可能得到加速,因?yàn)镃PU總的運(yùn)算能力就那些
CPU密集型任務(wù)配置盡可能少的線程數(shù)量
IO密集型
參考公式:CPU核數(shù)/(1-阻塞系數(shù)) 阻塞系數(shù)在0.8~0.9之間
由于IO密集型任務(wù)線程并不是一直在執(zhí)行任務(wù),則應(yīng)配置經(jīng)可能多的線程,如CPU核數(shù) * 2
IO密集型,即該任務(wù)需要大量的IO,即大量的阻塞。在單線程上運(yùn)行IO密集型的任務(wù)會(huì)導(dǎo)致浪費(fèi)大量的 CPU運(yùn)算能力浪費(fèi)在等待。
所以在IO密集型任務(wù)中使用多線程可以大大的加速程序運(yùn)行,即使在單核CPU上,這種加速主要就是利用 了被浪費(fèi)掉的阻塞時(shí)間。
IO密集型時(shí),大部分線程都阻塞,故需要多配置線程數(shù):
例如八核CPU:,利用公式,約為:8/(1-0,9)=80個(gè)線程
作者 | y浴血
來源 | cnblogs.com/yuxueyyz/p/14999837.html

