Java線程池使用與原理詳解
線程池是什么?
我們可以利用java很容易創(chuàng)建一個(gè)新線程,同時(shí)操作系統(tǒng)創(chuàng)建一個(gè)線程也是一筆不小的開銷。所以基于線程的復(fù)用,就提出了線程池的概念,我們使用線程池創(chuàng)建出若干個(gè)線程,執(zhí)行完一個(gè)任務(wù)后,該線程會(huì)存在一段時(shí)間(用戶可以設(shè)定空閑線程的存活時(shí)間,后面會(huì)介紹),等到新任務(wù)來的時(shí)候就直接復(fù)用這個(gè)空閑線程,這樣就省去了創(chuàng)建、銷毀線程損耗。當(dāng)然空閑線程也會(huì)是一種資源的浪費(fèi)(所有才有空閑線程存活時(shí)間的限制),但總比頻繁的創(chuàng)建銷毀線程好太多。
下面是我的測(cè)試代碼
/* * @TODO 線程池測(cè)試 */ @Test public void threadPool(){ /*java提供的統(tǒng)計(jì)線程運(yùn)行數(shù),一開始設(shè)置其值為50000,每一個(gè)線程任務(wù)執(zhí)行完 * 調(diào)用CountDownLatch#coutDown()方法(其實(shí)就是自減1) * 當(dāng)所有的線程都執(zhí)行完其值就為0 */ CountDownLatch count = new CountDownLatch(50000); long start = System.currentTimeMillis(); Executor pool = Executors.newFixedThreadPool(10);//開啟線程池最多會(huì)創(chuàng)建10個(gè)線程 for(int i=0;i<50000;i++){ pool.execute(new Runnable() { @Override public void run() { System.out.println("hello"); count.countDown(); } }); } while(count.getCount()!=0){//堵塞等待5w個(gè)線程運(yùn)行完畢 } long end = System.currentTimeMillis(); System.out.println("50個(gè)線程都執(zhí)行完了,共用時(shí):"+(end-start)+"ms"); } /** *@TODO 手動(dòng)創(chuàng)建線程測(cè)試 */ @Test public void thread(){ CountDownLatch count = new CountDownLatch(50000); long start = System.currentTimeMillis(); for(int i=0;i<50000;i++){ Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("hello"); count.countDown(); } }); thread.start(); } while(count.getCount()!=0){//堵塞等待5w個(gè)線程運(yùn)行完畢 } long end = System.currentTimeMillis(); System.out.println("50000個(gè)線程都執(zhí)行完了,共用時(shí):"+(end-start)+"ms"); }
使用線程池5w線程運(yùn)行完大約為400ms,不使用線程池運(yùn)行大約為4350ms左右,其效率可見一斑(讀者可以自行測(cè)試,不過由于電腦配置不一樣,跑出來的數(shù)據(jù)會(huì)有差別,但使用線程池絕對(duì)是比創(chuàng)建線程要快的)。
java如何使用線程池?
上面的測(cè)試代碼中已經(jīng)使用了線程池,下面正式介紹一下。
java所有的線程池最頂層是一個(gè)Executor接口,其只有一個(gè)execute方法,用于執(zhí)行所有的任務(wù),java又提供了ExecutorService接口繼承自Executor并且擴(kuò)充了一下方法,在往下就是AbstractExecutorService這個(gè)抽象類,其實(shí)現(xiàn)了ExecutorService,最后就是ThreadPoolExecutor其繼承自上面的抽象類,我們常使用的java線程池就是創(chuàng)建的這個(gè)類的實(shí)例。
而上面我們使用Executors是一個(gè)工具類,它就是一個(gè)語法糖,為我們把各種不同的業(yè)務(wù)的線程池參數(shù)進(jìn)行封裝,進(jìn)行new操作。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
上面就是Executors.newFixedThreadPool(10)的源碼。
下面重點(diǎn)來了,說一說ThreadPoolExecutor構(gòu)造方法各參數(shù)的意思。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
上面這個(gè)構(gòu)造方法是最全的。
下面我們根據(jù)源碼來解釋部分參數(shù)意思,這樣更有說服力。
下面是ThreadPoolExecutor#execute方法,就是我們上面接口調(diào)用的execute實(shí)際執(zhí)行者。
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); }
ctl是一個(gè)AtomicInteger實(shí)例,是一個(gè)提供了原子語句的CAS操作的類,它用來記錄線程池中當(dāng)前運(yùn)行的線程數(shù)量加上-2^29,workCountOf方法就取得其絕對(duì)值(可以去看源碼如何實(shí)現(xiàn)),當(dāng)其小于corePoolSize時(shí),會(huì)調(diào)用addWorker方法(是用來創(chuàng)建一個(gè)新Workder,Workder會(huì)創(chuàng)建一個(gè)Thread,所以就是創(chuàng)建線程的方法),addWorkd創(chuàng)建線程過程中會(huì)跟corePoolSize或者maxnumPoolSize的值比較(當(dāng)傳入true會(huì)根corePoolSize比較,false會(huì)根據(jù)maxnumPoolSize比較,大于等于其值會(huì)創(chuàng)建失?。?梢娙绾萎?dāng)前運(yùn)行中的線程數(shù)量小于corePoolSize就是創(chuàng)建并且也會(huì)創(chuàng)建成功(
只簡單的討論線程池Running狀態(tài)下)。
如果當(dāng)運(yùn)行中線程數(shù)大于等于corePoolSize時(shí),進(jìn)入第二個(gè)if,isRunning是跟SHUTDOWN(其值=0)比較,之前說過c等于當(dāng)前運(yùn)行的線程數(shù)量加上-2^29,如果當(dāng)前當(dāng)前運(yùn)行的線程數(shù)據(jù)達(dá)到2^29時(shí)其值就=0,isRunning返回false,else中在執(zhí)行addWorkd也會(huì)返回false(addWorkd也對(duì)其進(jìn)行了檢驗(yàn)),所以這表示線程池最多能支持2^29個(gè)線程同時(shí)運(yùn)行(足夠用了)。
workQueue.offer(command)就是將runnable加入等待隊(duì)列,加入等待隊(duì)列后runWorker方法會(huì)從隊(duì)列中獲取任務(wù)執(zhí)行的。如果當(dāng)前隊(duì)列采用的是有界隊(duì)列(ArrayBlockingQueue)當(dāng)隊(duì)列滿了offer就會(huì)返回false,這是就進(jìn)入else if,看!這里傳入了false,說明這里要跟maxnumPoolSize比較了,如果這里運(yùn)行的線程數(shù)大于等于maxnumPoolSize,那么這個(gè)線程任務(wù)就要被線程池拒絕了,執(zhí)行reject(command),拒絕方法中使用了我們ThreadPoolExecutor構(gòu)造方法中的RejectedExecutionHandler(拒絕策略),后面再詳細(xì)解釋。
經(jīng)過上面的結(jié)合源碼的介紹,下面對(duì)們ThreadPoolExecutor的參數(shù)介紹就好理解了。
線程池中線程創(chuàng)建和拒絕策略
corePoolSize,maxnumPoolSize,BlockingQueue這三個(gè)要一塊說
當(dāng)線程池運(yùn)行的線程小于corePoolSize時(shí),來一個(gè)新線程任務(wù)總是會(huì)新建一個(gè)線程來執(zhí)行;當(dāng)大于corePoolSize就會(huì)把任務(wù)加入到等待隊(duì)列blockingQueue中,如果你傳入的BlockingQueue是一個(gè)無界隊(duì)列(LinkedBlockingQueue)這是隊(duì)列可以存放“無窮多”的任務(wù),所有總是會(huì)加入隊(duì)列成功,跟maxnumPoolSize就沒關(guān)系了,這也表示線程池中線程數(shù)最多為corePoolSize個(gè);但是如果你傳入的是有界隊(duì)列(ArrayBlockingQueue,SynchronousQueue),當(dāng)隊(duì)列滿時(shí),并且線程數(shù)小于maxmunPoolSize就是創(chuàng)建新的線程直至線程數(shù)大于maxnumPoolSize;如果當(dāng)線程數(shù)量大于maxnumPoolSize時(shí),在加入任務(wù)就會(huì)被線程池拒絕。
RejectedExecutionHandler拒絕策略java給實(shí)現(xiàn)了4個(gè)AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardPolicy用戶也可以自己實(shí)現(xiàn)該接口實(shí)現(xiàn)自己的拒絕策略;第一個(gè)就是直接拋出異常,我們可以進(jìn)行trycatch處理;第二個(gè)就是該新任務(wù)直接運(yùn)行;第三個(gè)是取消隊(duì)列中最老的;第四個(gè)是取消當(dāng)前任務(wù)。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
如何用java計(jì)算兩個(gè)時(shí)間相差多少小時(shí)
最近工作中遇到需要計(jì)算時(shí)間差,下面這篇文章主要給大家介紹了關(guān)于如何用java計(jì)算兩個(gè)時(shí)間相差多少小時(shí)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12Java中LinkedList詳解和使用示例_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
LinkedList 是一個(gè)繼承于AbstractSequentialList的雙向鏈表。它也可以被當(dāng)作堆棧、隊(duì)列或雙端隊(duì)列進(jìn)行操作。接下來通過示例代碼給大家詳細(xì)介紹java中l(wèi)inkedlist的使用,需要的朋友參考下吧2017-05-05詳解Spring依賴注入的三種方式以及優(yōu)缺點(diǎn)
IoC?和?DI?是?Spring?中最重要的兩個(gè)概念,其中?IoC(Inversion?of?Control)為控制反轉(zhuǎn)的思想,而?DI(Dependency?Injection)依賴注入為其(IoC)具體實(shí)現(xiàn)。那么?DI?實(shí)現(xiàn)依賴注入的方式有幾種?這些注入方式又有什么不同?本文就來和大家一起詳細(xì)聊聊2022-08-08Java經(jīng)典面試題匯總:網(wǎng)絡(luò)編程
本篇總結(jié)的是Java 網(wǎng)絡(luò)編程相關(guān)的面試題,后續(xù)會(huì)持續(xù)更新,希望我的分享可以幫助到正在備戰(zhàn)面試的實(shí)習(xí)生或者已經(jīng)工作的同行,如果發(fā)現(xiàn)錯(cuò)誤還望大家多多包涵,不吝賜教,謝謝2021-07-07利用javaFX實(shí)現(xiàn)移動(dòng)一個(gè)小球的示例代碼
這篇文章主要介紹了利用javaFX實(shí)現(xiàn)移動(dòng)一個(gè)小球的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09springcloud整合到項(xiàng)目中無法啟動(dòng)報(bào)錯(cuò)Failed to start bean&n
這篇文章主要介紹了springcloud整合到項(xiàng)目中無法啟動(dòng)報(bào)錯(cuò)Failed to start bean 'eurekaAutoServiceRegistration'問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01idea啟動(dòng)springboot報(bào)錯(cuò): 找不到或無法加載主類問題
這篇文章主要介紹了idea啟動(dòng)springboot報(bào)錯(cuò): 找不到或無法加載主類問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Java?SE循環(huán)一些基本練習(xí)題總結(jié)
循環(huán)語句可以在滿足循環(huán)條件的情況下,反復(fù)執(zhí)行某一段代碼,這段被重復(fù)執(zhí)行的代碼被稱為循環(huán)體語句,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Java?SE循環(huán)一些基本練習(xí)題,需要的朋友可以參考下2024-03-03