Java ExecutorService四種線程池使用詳解
1.引言
合理利用線程池能夠帶來三個(gè)好處。第一:降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。第二:提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。但是要做到合理的利用線程池,必須對其原理了如指掌。
2.線程池使用
Executors提供的四種線程 1.newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。 2.newFixedThreadPool 創(chuàng)建一個(gè)定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊(duì)列中等待。 3.newScheduledThreadPool 創(chuàng)建一個(gè)定長線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。 4.newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。
1.newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。示例如下
ExecutorService executorService = Executors.newCachedThreadPool(); for(int i=0;i<5;i++){ final int index = i; try { Thread.sleep(index * 1000); } catch (InterruptedException e) { e.printStackTrace(); } executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "," +index); } }); } //控制臺信息 pool-1-thread-1,0 pool-1-thread-1,1 pool-1-thread-1,2 pool-1-thread-1,3 pool-1-thread-1,4
2.newFixedThreadPool創(chuàng)建一個(gè)定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊(duì)列中等待。示例如下
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4); for(int i=0;i<5;i++) { final int index = i; fixedThreadPool.execute(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + ", " + index); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } //控制臺信息 pool-1-thread-1,0 pool-1-thread-2,1 pool-1-thread-3,2 pool-1-thread-4,3 pool-1-thread-1,4
3.newScheduledThreadPool 創(chuàng)建一個(gè)定長線程池,支持周期和定時(shí)任務(wù)示例如下
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); System.out.println("before:" + System.currentTimeMillis()/1000); scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.out.println("延遲3秒執(zhí)行的哦 :" + System.currentTimeMillis()/1000); } }, 3, TimeUnit.SECONDS); System.out.println("after :" +System.currentTimeMillis()/1000); //控制臺信息 before:1518012703 after :1518012703 延遲3秒執(zhí)行的哦 :1518012706 System.out.println("before:" + System.currentTimeMillis()/1000); scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("延遲1秒之后,3秒執(zhí)行一次:" +System.currentTimeMillis()/1000); } }, 1, 3, TimeUnit.SECONDS); System.out.println("after :" +System.currentTimeMillis()/1000);
控制臺消息
before:1518013024
after :1518013024
延遲1秒之后,3秒執(zhí)行一次:1518013025
延遲1秒之后,3秒執(zhí)行一次:1518013028
延遲1秒之后,3秒執(zhí)行一次:1518013031
4.newSingleThreadExecutor創(chuàng)建一個(gè)單線程化的線程池,只會用工作線程來執(zhí)行任務(wù),保證順序,示例如下
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i=0;i<10;i++) { final int index = i; singleThreadExecutor.execute(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + "," + index); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
控制臺信息
pool-1-thread-1,0
pool-1-thread-1,1
pool-1-thread-1,2
pool-1-thread-1,3
pool-1-thread-1,4
向線程池提交任務(wù) ThreadPoolExecutor類中execute()和submit()區(qū)別 execute()方法實(shí)際上是Executor中聲明的方法,在ThreadPoolExecutor進(jìn)行了具體的實(shí)現(xiàn),這個(gè)方法是ThreadPoolExecutor的核心方法,通過這個(gè)方法可以向線程池提交一個(gè)任務(wù),交由線程池去執(zhí)行。
submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經(jīng)有了具體的實(shí)現(xiàn),在ThreadPoolExecutor中并沒有對其進(jìn)行重寫,這個(gè)方法也是用來向線程池提交任務(wù)的,但是它和execute()方法不同,它能夠返回任務(wù)執(zhí)行的結(jié)果,通過源碼查看submit()方法的實(shí)現(xiàn),會發(fā)現(xiàn)它實(shí)際上還是調(diào)用的execute()方法,只不過它利用了Future來獲取任務(wù)執(zhí)行結(jié)果。
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; }
線程池的關(guān)閉 我們可以通過調(diào)用線程池的shutdown或shutdownNow方法來關(guān)閉線程池,但是它們的實(shí)現(xiàn)原理不同,shutdown的原理是只是將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài),然后中斷所有沒有正在執(zhí)行任務(wù)的線程。shutdownNow的原理是遍歷線程池中的工作線程,然后逐個(gè)調(diào)用線程的interrupt方法來中斷線程,所以無法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無法終止。shutdownNow會首先將線程池的狀態(tài)設(shè)置成STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程,并返回等待執(zhí)行任務(wù)的列表。
只要調(diào)用了這兩個(gè)關(guān)閉方法的其中一個(gè),isShutdown方法就會返回true。當(dāng)所有的任務(wù)都已關(guān)閉后,才表示線程池關(guān)閉成功,這時(shí)調(diào)用isTerminaed方法會返回true。至于我們應(yīng)該調(diào)用哪一種方法來關(guān)閉線程池,應(yīng)該由提交到線程池的任務(wù)特性決定,通常調(diào)用shutdown來關(guān)閉線程池,如果任務(wù)不一定要執(zhí)行完,則可以調(diào)用shutdownNow。
3. 線程池的分析
流程分析:線程池的主要工作流程如下圖: Java線程池主要工作流程
從上圖我們可以看出,當(dāng)提交一個(gè)新任務(wù)到線程池時(shí),線程池的處理流程如下:
- 首先線程池判斷基本線程池是否已滿?沒滿,創(chuàng)建一個(gè)工作線程來執(zhí)行任務(wù)。滿了,則進(jìn)入下個(gè)流程。
- 其次線程池判斷工作隊(duì)列是否已滿?沒滿,則將新提交的任務(wù)存儲在工作隊(duì)列里。滿了,則進(jìn)入下個(gè)流程。
- 最后線程池判斷整個(gè)線程池是否已滿?沒滿,則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù),滿了,則交給飽和策略來處理這個(gè)任務(wù)。
**源碼分析。**上面的流程分析讓我們很直觀的了解的線程池的工作原理,讓我們再通過源代碼來看看是如何實(shí)現(xiàn)的。線程池執(zhí)行任務(wù)的方法如下:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } 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); } else if (!addWorker(command, false)) reject(command); }
工作線程。線程池創(chuàng)建線程時(shí),會將線程封裝成工作線程Worker,Worker在執(zhí)行完任務(wù)后,還會無限循環(huán)獲取工作隊(duì)列里的任務(wù)來執(zhí)行。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Redis Java Lettuce驅(qū)動框架原理解析
這篇文章主要介紹了Redis Java Lettuce驅(qū)動框架原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12SpringBoot開啟虛擬線程的實(shí)現(xiàn)流程
虛擬線程(Virtual?Thread)也稱協(xié)程或纖程,是一種輕量級的線程實(shí)現(xiàn),與傳統(tǒng)的線程以及操作系統(tǒng)級別的線程(也稱為平臺線程)相比,它的創(chuàng)建開銷更小、資源利用率更高,本文給大家介紹了SpringBoot如何開啟虛擬線程,需要的朋友可以參考下2024-06-06詳解spring中使用Elasticsearch的代碼實(shí)現(xiàn)
本篇文章主要介紹了詳解spring中使用Elasticsearch的代碼實(shí)現(xiàn),具有一定的參考價(jià)值,有興趣的可以了解一下2017-05-05springcloud實(shí)現(xiàn)注冊中心Eureka
這篇文章主要介紹了springcloud實(shí)現(xiàn)注冊中心Eureka,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02SpringBoot2底層注解@ConfigurationProperties配置綁定
這篇文章主要介紹了SpringBoot2底層注解@ConfigurationProperties配置綁定,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05使用SpringBoot發(fā)送郵箱驗(yàn)證碼的簡單實(shí)現(xiàn)
這篇文章主要介紹了使用SpringBoot發(fā)送郵箱驗(yàn)證碼的簡單實(shí)現(xiàn),咱們今天來講使用QQ郵箱來發(fā)送和接收驗(yàn)證碼,首先來介紹一下它在SpringBoot項(xiàng)目中的具體應(yīng)用,需要的朋友可以參考下2023-04-04