Java线程池原理及使用
java中的線程池是運(yùn)用場(chǎng)景最多的并發(fā)框架。在開發(fā)過程中,合理的使用線程池能夠帶來下面的一些好處:
1、降低資源的消耗。
2、提高響應(yīng)速度。
3、提高線程的可管理型。
1.1、線程池ThreadPoolExecutor工作原理
講解之前,我們先看一張?jiān)韴D
ThreadPoolExecutor執(zhí)行execute方法有4種情況:
1)如果當(dāng)前運(yùn)行的線程少于corePoolSize,則創(chuàng)建新的線程來執(zhí)行任務(wù)。
2)如果運(yùn)行的線程等于或者多余corePoolSize,則將任務(wù)加入BlockingQueue中,在等待隊(duì)列中,等待有新的線程可以運(yùn)行。
3)如果BlockingQueue隊(duì)列滿了,且沒有超過maxPoolSize,則創(chuàng)建新的線程來處理任務(wù)。
4)如果創(chuàng)建的線程超過maxPoolSize,任務(wù)會(huì)拒絕,并調(diào)用RejectExecutionHandler.rejectedExecution()方法。
1.2、線程池的使用
1.2.1、線程池的創(chuàng)建
一般我們可以通過ThreadPoolExecutor來創(chuàng)建一個(gè)線程池。
在ThreadPoolExecutor類中提供了四個(gè)構(gòu)造方法:
我們通過
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);創(chuàng)建一個(gè)新的線程池。
下面我們介紹一下需要輸入的幾個(gè)參數(shù)的意義:
1)corePoolSize:核心池的大小,這個(gè)參數(shù)跟后面講述的線程池的實(shí)現(xiàn)原理有非常大的關(guān)系。在創(chuàng)建了線程池后,默認(rèn)情況下,線程池中并沒有任何線程,而是等待有任務(wù)到來才創(chuàng)建線程去執(zhí)行任務(wù),除非調(diào)用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個(gè)方法的名字就可以看出,是預(yù)創(chuàng)建線程的意思,即在沒有任務(wù)到來之前就創(chuàng)建corePoolSize個(gè)線程或者一個(gè)線程。默認(rèn)情況下,在創(chuàng)建了線程池后,線程池中的線程數(shù)為0,當(dāng)有任務(wù)來之后,就會(huì)創(chuàng)建一個(gè)線程去執(zhí)行任務(wù),當(dāng)線程池中的線程數(shù)目達(dá)到corePoolSize后,就會(huì)把到達(dá)的任務(wù)放到緩存隊(duì)列當(dāng)中;
2) maximumPoolSize:線程池最大線程數(shù),這個(gè)參數(shù)也是一個(gè)非常重要的參數(shù),它表示在線程池中最多能創(chuàng)建多少個(gè)線程;
3)keepAliveTime:表示線程沒有任務(wù)執(zhí)行時(shí)最多保持多久時(shí)間會(huì)終止。默認(rèn)情況下,只有當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí),keepAliveTime才會(huì)起作用,直到線程池中的線程數(shù)不大于corePoolSize,即當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí),如果一個(gè)線程空閑的時(shí)間達(dá)到keepAliveTime,則會(huì)終止,直到線程池中的線程數(shù)不超過corePoolSize。但是如果調(diào)用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數(shù)不大于corePoolSize時(shí),keepAliveTime參數(shù)也會(huì)起作用,直到線程池中的線程數(shù)為0;
- unit:參數(shù)keepAliveTime的時(shí)間單位,有7種取值,在TimeUnit類中有7種靜態(tài)屬性:
4) workQueue:一個(gè)阻塞隊(duì)列,用來存儲(chǔ)等待執(zhí)行的任務(wù),這個(gè)參數(shù)的選擇也很重要,會(huì)對(duì)線程池的運(yùn)行過程產(chǎn)生重大影響,一般來說,這里的阻塞隊(duì)列有以下幾種選擇:
- ArrayBlockingQueue:一個(gè)基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列。
- LinkedBlockingQueue:一個(gè)基于鏈表的阻塞隊(duì)列,吞吐量要高于ArrayBlockingQueue。
- SynchronousQueue:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列。每次插入操作必須等到另外一個(gè)線程調(diào)用移除操作,否則一直處于阻塞狀態(tài)。吞吐量要高于LinkedBlockingQueue。
- PriorityBlockingQueue:一個(gè)具有優(yōu)先級(jí)的無(wú)線阻塞隊(duì)列。
ArrayBlockingQueue和PriorityBlockingQueue使用較少,一般使用LinkedBlockingQueue和Synchronous。線程池的排隊(duì)策略與BlockingQueue有關(guān)。
5)threadFactory:線程工廠,主要用來創(chuàng)建線程;
6)RejectedExecutionHandler:當(dāng)隊(duì)列和線程池都滿了,將會(huì)執(zhí)行下面的策略,jdk1.5中提供有以下四種策略:
- ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。
- ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。
- ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)
- ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)
1.2.2、如何向線程池提交任務(wù)
向線程池提交任務(wù),提供了兩種方法,分別是execute()和submit()方法。
1)execute()方法
execute方法用于提交不需要返回值的任務(wù),所以也就意味著無(wú)法判斷是否執(zhí)行成功。
pool.execute(new Runnable(){@Overridepublic void run() {System.out.println("使用execute提交任務(wù).");}});2)submit方法
submit方法可以用于提交需要有返回值的任務(wù)。線程池會(huì)返回一個(gè)future類型的對(duì)象,通過這個(gè)future對(duì)象可以判讀是否執(zhí)行成功,并且還可以通過get()方法來獲取返回值。
Runnable task = null;Future<Object> future = (Future<Object>) pool.submit(task);try {future.get();//獲取返回值} catch (InterruptedException e) {//中斷異常處理// TODO Auto-generated catch blocke.printStackTrace();} catch (ExecutionException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {//關(guān)閉線程池pool.shutdown();}1.2.3、關(guān)閉線程池
在上一節(jié)中,我們?cè)诋惓5奶幚砗竺?#xff0c;我們就使用到了shutdown()方法來關(guān)閉線程池。
在關(guān)閉線程池的時(shí)候,這里有兩個(gè)方法可以調(diào)用,分別是shutdown和shutdownNow方法。
1.3、線程池使用實(shí)例
1.3.1、線程池的使用實(shí)例
這個(gè)實(shí)例我們使用自定義的拒絕策略,因?yàn)閖dk的策略并不是很完美
public class MyRejected implements RejectedExecutionHandler{public MyRejected(){}@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("自定義處理..");System.out.println("當(dāng)前被拒絕任務(wù)為:" + r.toString());}}然后我們定義一個(gè)任務(wù)類
public class MyTask implements Runnable {private int taskId;private String taskName;public MyTask(int taskId, String taskName){this.taskId = taskId;this.taskName = taskName;}public int getTaskId() {return taskId;}public void setTaskId(int taskId) {this.taskId = taskId;}public String getTaskName() {return taskName;}public void setTaskName(String taskName) {this.taskName = taskName;}@Overridepublic void run() {try {System.out.println("run taskId =" + this.taskId);Thread.sleep(5*1000);//System.out.println("end taskId =" + this.taskId);} catch (InterruptedException e) {e.printStackTrace();} }public String toString(){return Integer.toString(this.taskId);}}最后,我們看一下任務(wù)執(zhí)行
public class UseThreadPoolExecutor1 {public static void main(String[] args) {/*** 在使用有界隊(duì)列時(shí),若有新的任務(wù)需要執(zhí)行,如果線程池實(shí)際線程數(shù)小于corePoolSize,則優(yōu)先創(chuàng)建線程,* 若大于corePoolSize,則會(huì)將任務(wù)加入隊(duì)列,* 若隊(duì)列已滿,則在總線程數(shù)不大于maximumPoolSize的前提下,創(chuàng)建新的線程,* 若線程數(shù)大于maximumPoolSize,則執(zhí)行拒絕策略。或其他自定義方式。* */ ThreadPoolExecutor pool = new ThreadPoolExecutor(1, //coreSize2, //MaxSize60, //60TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3) //指定一種隊(duì)列 (有界隊(duì)列)//new LinkedBlockingQueue<Runnable>(), new MyRejected()//, new DiscardOldestPolicy());MyTask mt1 = new MyTask(1, "任務(wù)1");MyTask mt2 = new MyTask(2, "任務(wù)2");MyTask mt3 = new MyTask(3, "任務(wù)3");MyTask mt4 = new MyTask(4, "任務(wù)4");MyTask mt5 = new MyTask(5, "任務(wù)5");MyTask mt6 = new MyTask(6, "任務(wù)6");pool.execute(mt1);pool.execute(mt2);pool.execute(mt3);/*pool.execute(mt4);pool.execute(mt5);pool.execute(mt6);*/pool.shutdown(); // pool.shutdownNow();} }執(zhí)行結(jié)果:
1)當(dāng)運(yùn)行<5個(gè)時(shí),可以正常運(yùn)行:
2)當(dāng)>5時(shí),因?yàn)榇笥诹俗畲笾?#xff0c;所以執(zhí)行了異常策略:
1.3.2、線程池的監(jiān)控參數(shù)或者其他api使用
當(dāng)我們需要對(duì)線程池進(jìn)行監(jiān)控時(shí),我們可以使用線程池提供的參數(shù)進(jìn)行監(jiān)控,可以使用下面的一些屬性。
- taskCount:線程池需要執(zhí)行的任務(wù)數(shù)量。
- completedTaskCount:線程池在運(yùn)行過程中已完成的數(shù)量。
- largestPoolSize:線程池里曾經(jīng)創(chuàng)建過的最大的線程數(shù)量。
- poolSize:線程池的線程數(shù)量。
- ActiveCount:獲取活動(dòng)的線程數(shù)量。
運(yùn)行結(jié)果:
1.4、如何合理的配置線程池的大小
一般需要根據(jù)任務(wù)的類型來配置線程池大小:
-
如果是CPU密集型任務(wù),就需要盡量壓榨CPU,參考值可以設(shè)為 NCPU+1
-
如果是IO密集型任務(wù),參考值可以設(shè)置為2*NCPU
-
建議使用有界隊(duì)列。因?yàn)橛薪珀?duì)列能夠增加系統(tǒng)的穩(wěn)定性和預(yù)警的能力,我們可以想象一下,當(dāng)我們使用無(wú)界隊(duì)列的時(shí)候,當(dāng)我們系統(tǒng)的后臺(tái)的線程池的隊(duì)列和線程池會(huì)越來越多,這樣當(dāng)達(dá)到一定的程度的時(shí)候,有可能會(huì)撐滿內(nèi)存,導(dǎo)致系統(tǒng)出現(xiàn)問題。當(dāng)我們是有界隊(duì)列的時(shí)候,當(dāng)我們系統(tǒng)的后臺(tái)的線程池的隊(duì)列和線程池滿了之后,會(huì)不斷的拋出異常的任務(wù),我們可以通過異常信息做一些事情。
當(dāng)然,這只是一個(gè)參考值,具體的設(shè)置還需要根據(jù)實(shí)際情況進(jìn)行調(diào)整,比如可以先將線程池大小設(shè)置為參考值,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載、資源利用率來進(jìn)行適當(dāng)調(diào)整。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Java线程池原理及使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: numpy学习(2):数组创建方式
- 下一篇: Java11正式发布了,我们该怎么办?