建筑培訓(xùn)網(wǎng)站網(wǎng)絡(luò)廣告策劃的內(nèi)容
目錄
1. 線程池相比于線程有什么優(yōu)點(diǎn)
2. 線程池的參數(shù)有哪些
3. 線程工廠有什么用
4. 說一下線程的優(yōu)先級(jí)
5. 說一下線程池的執(zhí)行流程
6. 線程池的拒絕策略有哪些
7. 如何實(shí)現(xiàn)自定義拒絕策略
8. 如何判斷線程池中的任務(wù)是否執(zhí)行完成
1. 線程池相比于線程有什么優(yōu)點(diǎn)
有時(shí)候面試官也會(huì)這么問 : 讓你介紹一下線程池,都是一樣的回答方式。
在實(shí)際生活中,一般都是使用線程池,而不使用普通的線程,使用線程池有以下幾個(gè)好處:
1. 降低資源消耗:線程池可以重復(fù)利用已經(jīng)創(chuàng)建好的線程,避免了頻繁創(chuàng)建和銷毀線程帶來的開銷。其次呢,線程池可以有效地管理和控制線程的數(shù)量,避免線程過多而導(dǎo)致資源浪費(fèi)。
2. 提高響應(yīng)速度:線程池中的線程是預(yù)先創(chuàng)建好的,所以當(dāng)任務(wù)來臨時(shí),它可以立即分配線程來進(jìn)行處理,提高了任務(wù)的響應(yīng)速度。
3. 提高系統(tǒng)穩(wěn)定性:線程池可以限制并發(fā)時(shí)的線程數(shù)量,避免因?yàn)榫€程過多而導(dǎo)致資源耗盡或系統(tǒng)崩潰。它可以合理的控制系統(tǒng)的負(fù)載,以提高系統(tǒng)的穩(wěn)定性。
4. 支持任務(wù)隊(duì)列:線程池通常會(huì)使用任務(wù)隊(duì)列來存儲(chǔ)待執(zhí)行的任務(wù)。當(dāng)線程池中的線程都在執(zhí)行任務(wù)時(shí),新的任務(wù)可以被放入任務(wù)隊(duì)列中排隊(duì)等待執(zhí)行,避免任務(wù)丟失或阻塞。
2. 線程池的參數(shù)有哪些
線程池 ThreadPoolExecutor 最多可以支持 7 個(gè)參數(shù)的設(shè)置(最少 5 個(gè)),底層源碼:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||// maximumPoolSize 必須大于 0, 且必須大于 corePoolSize 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;
}
7 個(gè)參數(shù)的含義如下 :
1.?corePoolSize:表示線程池的常駐核心線程數(shù)
????????這個(gè)值如果設(shè)為 0, 則表示在沒有任務(wù)的時(shí)候,銷毀線程池;如果大于 0,即使沒有任務(wù)也會(huì)保證線程池的線程數(shù)量等于這個(gè)值。(這個(gè)值如果設(shè)置的比較小,則會(huì)頻繁的創(chuàng)建和銷毀線程;如果設(shè)置的比較大,則浪費(fèi)系統(tǒng)資源,實(shí)際工作中根據(jù)業(yè)務(wù)場景來調(diào)整)
2.?maximumPoolSize:表示線程池在任務(wù)最多的時(shí)候,最大可以創(chuàng)建的線程數(shù)
????????官方規(guī)定這個(gè)值必須大于 0。也必須大于等于?corePoolSize,(此值只有在任務(wù)比較多的時(shí)候,且任務(wù)隊(duì)列已經(jīng)存滿的時(shí)候,才會(huì)用到)
3.?keepAliveTime:表示臨時(shí)線程(最大線程數(shù)-核心線程數(shù))的存活時(shí)間
????????當(dāng)線程池空閑時(shí)并且超過了這個(gè)時(shí)間,就會(huì)銷毀臨時(shí)線程,直到線程池中的線程數(shù)量等于 corePoolSize 為止。
4. unit:表示臨時(shí)線程的存活單位
5. workQueue:表示線程池執(zhí)行的任務(wù)隊(duì)列
????????當(dāng)線程池的所有線程都在處理任務(wù)時(shí),新加入的任務(wù)就會(huì)緩存到任務(wù)隊(duì)列中,排隊(duì)等待執(zhí)行。
6.threadFactory:表示線程的創(chuàng)建工廠
????????可以用來設(shè)置線程名稱,線程優(yōu)先級(jí),以及線程的類型(前后臺(tái)線程)等內(nèi)容。
7. RejectedExecutionHandler:表示指定線程池的拒絕策略
????????當(dāng)線程池中的核心線程數(shù)都在執(zhí)行任務(wù),任務(wù)隊(duì)列此也已經(jīng)滿了,并且線程池中的線程數(shù)已經(jīng)達(dá)到設(shè)置的最大線程數(shù)了(不能創(chuàng)建新線程了),就會(huì)使用到拒絕策略,它是一種限流保護(hù)的機(jī)制。
3. 線程工廠有什么用
通過線程工廠可以設(shè)置線程池中可以創(chuàng)建的最大線程數(shù),設(shè)置線程的名稱,線程的優(yōu)先級(jí)以及線程的類型等內(nèi)容。
ThreadFactory threadFactory = new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {// 創(chuàng)建線程池中的線程Thread thread = new Thread(r);// 設(shè)置線程名稱thread.setName("Thread-" + r.hashCode());// 設(shè)置線程的優(yōu)先級(jí) (1-10) 最大 10thread.setPriority(Thread.MAX_PRIORITY);// 設(shè)置線程的類型 (前臺(tái)/后臺(tái)線程)thread.setDaemon(false);// ....return thread;}
};
4. 說一下線程的優(yōu)先級(jí)
線程的優(yōu)先級(jí)用整數(shù)表示,范圍在 1-10 之間,并且數(shù)字越大表示的優(yōu)先級(jí)越高,線程的默認(rèn)優(yōu)先級(jí)為 5。
設(shè)置線程優(yōu)先級(jí)的作用:
????????線程優(yōu)先級(jí)表示當(dāng)前線程被調(diào)度的權(quán)重,也就是說線程的優(yōu)先級(jí)越高,表示線程被被調(diào)度執(zhí)行的可能性就越大,它會(huì)給線程調(diào)度器一個(gè)建議,具體是不是優(yōu)先級(jí)越高的越先執(zhí)行是不一定的。它這樣設(shè)計(jì)的目的就是為了防止線程餓死。
在 Java 中,可以通過 Thread 類的 setPriority() 和 個(gè)體 getPriority()? 來設(shè)置和獲取線程的優(yōu)先級(jí)
5. 說一下線程池的執(zhí)行流程
線程池的執(zhí)行流程通過下圖進(jìn)行梳理:
由于線程池是以懶加載的方式進(jìn)行創(chuàng)建線程的,所以線程池創(chuàng)建第一個(gè)線程的時(shí)候,它普通的創(chuàng)建線程沒有區(qū)別。
- 當(dāng)任務(wù)來臨時(shí),它會(huì)先去判斷一下實(shí)際線程數(shù)是否大于核心線程數(shù),如果小于等于核心線程數(shù),說明此時(shí)任務(wù)比較少,所以此時(shí)只需要?jiǎng)?chuàng)建一個(gè)新的線程來執(zhí)行任務(wù)就完事了。
- 如果實(shí)際線程數(shù)大于核心線程數(shù)了,然后再去判斷任務(wù)隊(duì)列有沒有滿,如果隊(duì)列沒有滿,那么就將新任務(wù)加入到任務(wù)隊(duì)列中排隊(duì)等待執(zhí)行。
- 如果隊(duì)列滿了,那么再去判斷實(shí)際線程數(shù)是否超過最大線程數(shù),如果沒有超過,那么創(chuàng)建一個(gè)新的線程來執(zhí)行任務(wù)就完事了。
- 如果已經(jīng)超過最大線程數(shù)了,那么就會(huì)執(zhí)行拒絕策略。
【聯(lián)系到快遞公司】
- 當(dāng)有新的快遞來臨了,我先看一下公司正式員工忙不忙的過來,如果有人空閑,那么安排空閑的人去干活。
- 如果公司的正式員工手頭都忙不過來,此時(shí)檢查一下快遞倉庫是否滿了,如果沒滿,那么就放入快遞倉庫排隊(duì)等待執(zhí)行。
- 如果快遞倉庫也滿了,老板此時(shí)還想掙這個(gè)錢,那么他會(huì)先計(jì)算出當(dāng)前要招多少員工,才能保證利益最大化,如果當(dāng)前公司總員工(正式+臨時(shí))還沒有超過預(yù)算值,那么就招臨時(shí)工來幫忙。
- 如果說招的臨時(shí)工已經(jīng)達(dá)到最大預(yù)算了,那么此時(shí)就會(huì)執(zhí)行拒絕策略了,我不接單了。
6. 線程池的拒絕策略有哪些
拒絕策略一共有 5 種,線程池內(nèi)置了 4 種和一種自定義拒絕策略,這四種拒絕策略分別是 :
1. AbortPolicy:終止策略,線程池會(huì)拋出異常并終止執(zhí)行此任務(wù);
? ? ? ? 在接不了訂單的情況下,公司還有部門接單,那么領(lǐng)導(dǎo)就會(huì)開內(nèi)部批斗大會(huì),并終止快遞流程。
2. CallerRunsPolciy:把任務(wù)交給添加此任務(wù)的(main)線程來執(zhí)行;
? ? ? ? 在接不了訂單的情況下,此時(shí)有一個(gè)卡車司機(jī)拉了一車快遞來了,于是跟貨車司機(jī)說:老弟啊,哥這也忙不過來了,你想不想掙個(gè)塊錢發(fā)家致富,于是就將這個(gè)任務(wù)交給卡車司機(jī)來執(zhí)行。
3. DiscardPolicy:忽略當(dāng)前任務(wù)(最新的任務(wù));
4. DiscardOldestPolicy:忽略任務(wù)隊(duì)列中最早加入的任務(wù),接收新任務(wù)(加入任務(wù)隊(duì)列);
線程池默認(rèn)的拒絕策略為 AbortPolicy 終止策略。
7. 如何實(shí)現(xiàn)自定義拒絕策略
代碼示例:
// 創(chuàng)建 runnable 對(duì)象
// ...ThreadPoolExecutor threadPool =new ThreadPoolExecutor(1, 1,100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r,ThreadPoolExecutor executor) {// 執(zhí)行自定義拒絕策略// 1.記錄日志,方便問題的追溯// 2.通知相關(guān)人員來解決問題System.out.println("執(zhí)行自定義拒絕策略");}});
// 添加并執(zhí)行 3 個(gè)任務(wù)
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
拒絕策略里面一般可以做兩件事:
- 記錄日志,以便問題的追溯;
- 通過相關(guān)人員來解決問題。
如果面試官此時(shí)還問:你在實(shí)際工作中/你的項(xiàng)目中,你使用的是哪一種拒絕策略?
????????如果不知道怎么回答,建議回答自定義拒絕策略,因?yàn)樗容^靈活,它可以去設(shè)置我想去設(shè)置的一些代碼,我在里面呢,首先我可以把錯(cuò)誤記錄下來,其次我可以給任務(wù)隊(duì)列發(fā)一個(gè)郵件,或者發(fā)一個(gè) MQ(消息不丟失)
8. 如何判斷線程池中的任務(wù)是否執(zhí)行完成
? ? ? ? " ??蜕嫌腥嗽诿嬖囉糜训臅r(shí)候,被問到王者榮耀 5 個(gè)人都加載完成才進(jìn)入游戲,用 Java 應(yīng)該怎么實(shí)現(xiàn),就和這個(gè)問題如出一轍 "
判斷線程池中的任務(wù)是否執(zhí)行完成,有三種方法:(線程池提供了兩種,還可以借助計(jì)數(shù)器)
1. 使用 getCompletedTaskCount() 統(tǒng)計(jì)已經(jīng)執(zhí)行完的任務(wù),使用?getTaskCount() 獲取線程池的總?cè)蝿?wù),將二者進(jìn)行對(duì)比,如果相等就說明線程池的任務(wù)執(zhí)行完了,反之沒有執(zhí)行完。
2. 使用 FutureTask 等待所有任務(wù)執(zhí)行完。(類似于 Thread 里面的 join)
3. 借助計(jì)數(shù)器 CountDownLatch 或 CyclicBarrier 來實(shí)現(xiàn)。
① getCompletedTaskCount() 方式
private static void isCompleteByTaskCount(ThreadPoolExecutor threadPool) {while(threadPool.getCompletedTaskCount()!= threadPool.getTaskCount()) {// 如果 已完成任務(wù)數(shù) != 總?cè)蝿?wù)數(shù), 就一直進(jìn)行自旋}
}
這種方式的缺點(diǎn):
- 如果已完成任務(wù)數(shù) != 總?cè)蝿?wù)數(shù),那么就會(huì)一直自旋,過于消耗系統(tǒng)性能;
- 判斷不夠精準(zhǔn),因?yàn)榫€程池是公用的,如果這個(gè)時(shí)候有其他線程來添加了新的任務(wù),那么總的任務(wù)數(shù)就變了,而我只想判斷屬于我的這幾個(gè)任務(wù)知否執(zhí)行完。
所以?getCompletedTaskCount() 并不是最優(yōu)的實(shí)現(xiàn)方式。
② FutureTask 的方式
代碼示例:
- 定義三個(gè)任務(wù) (callable)
- 調(diào)用線程池提供的 submit 方法 (可執(zhí)行回調(diào)任務(wù)/非回調(diào)任務(wù))
- 調(diào)用任務(wù)的 get() 方法 (同步阻塞,類似于 thread.join())
public static void main(String[] args) throws ExecutionException,InterruptedException {// 創(chuàng)建固定大小的線程池ExecutorService executor = Executors.newFixedThreadPool(3);// 創(chuàng)建 3 個(gè)任務(wù)FutureTask<Integer> task1 = new FutureTask<>(() -> {System.out.println("task1 開始執(zhí)行");Thread.sleep(2000);System.out.println("task1 執(zhí)行結(jié)束");return 1;});FutureTask<Integer> task2 = new FutureTask<>(() -> {System.out.println("task2 開始執(zhí)行");Thread.sleep(2000);System.out.println("task2 執(zhí)行結(jié)束");return 2;});FutureTask<Integer> task3 = new FutureTask<>(() -> {System.out.println("task3 開始執(zhí)行");Thread.sleep(2000);System.out.println("task3 執(zhí)行結(jié)束");return 3;});// 提交 3 個(gè)任務(wù)給線程池executor.submit(task1);executor.submit(task2);executor.submit(task3);// 等所有任務(wù)執(zhí)行完畢并獲取結(jié)果 (同步阻塞)int ret1 = task1.get();int ret2 = task1.get();int ret3 = task1.get();
}
執(zhí)行結(jié)果 >>>
③ 使用 CountDownLatch 或?CyclicBarrier
這個(gè)問題,回答前兩種方式其實(shí)已經(jīng)夠了,此處多介紹一個(gè) CountDownLatch。
代碼示例:
- 遞歸枚舉出所有 HTML 文件
- 通過線程池解析 HTML 文件
- 解析完所有的 HTML 文件后再執(zhí)行后續(xù)的業(yè)務(wù)邏輯
public void runByThread() throws InterruptedException {ArrayList<File> files = new ArrayList<>();enumFile(INPUT_PATH,files); // 遞歸枚舉出所有 HTML 文件ExecutorService executorService = Executors.newFixedThreadPool(4);CountDownLatch latch = new CountDownLatch(files.size());// 遍歷文件,多線程解析文件for(File f:files) {executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("開始解析: " + f.getAbsolutePath());parseHTML(f); // 解析文件latch.countDown(); // 調(diào)用一次這個(gè)方法表示一個(gè)線程已經(jīng)執(zhí)行完了}});}latch.await(); // 阻塞(死等) -> 直到所有的線程都調(diào)用了 countDown()// 執(zhí)行后續(xù)的業(yè)務(wù)邏輯System.out.println("索引制作完畢!");
}
那么上述三個(gè)步驟,4 個(gè)線程在執(zhí)行解析文件的時(shí)候,很可能會(huì)存在這樣一種情況:
這些文件都 submit 完了,但是還沒有執(zhí)行完,這是很有可能的。而想要執(zhí)行后續(xù)的業(yè)務(wù)邏輯,那么就得保證這些文件全部被執(zhí)行完了,所以此處 CountDownLatch 的作用就是為了保證這一點(diǎn)的
如何保證? 通過調(diào)用兩個(gè)方法 :
- latch.await()
- latch.countDown()
第一個(gè)方法的作用是阻塞,它會(huì)一直進(jìn)行死等,直到所有的線程都執(zhí)行完了(全部都調(diào)用了 countDown() 方法),才結(jié)束阻塞。
第二個(gè)方法的作用是用來統(tǒng)計(jì)線程的執(zhí)行情況,有一個(gè)線程執(zhí)行完了,計(jì)數(shù)器就 + 1。