Java 使用線程池執(zhí)行多個(gè)任務(wù)的示例
在執(zhí)行一系列帶有IO操作(例如下載文件),且互不相關(guān)的異步任務(wù)時(shí),采用多線程可以很極大的提高運(yùn)行效率。線程池包含了一系列的線程,并且可以管理這些線程。例如:創(chuàng)建線程,銷(xiāo)毀線程等。本文將介紹如何使用Java中的線程池執(zhí)行任務(wù)。
1 任務(wù)類(lèi)型
在使用線程池執(zhí)行任務(wù)之前,我們弄清楚什么任務(wù)可以被線程池調(diào)用。按照任務(wù)是否有返回值可以將任務(wù)分為兩種,分別是實(shí)現(xiàn)Runnable的任務(wù)類(lèi)(無(wú)參數(shù)無(wú)返回值)和實(shí)現(xiàn)Callable接口的任務(wù)類(lèi)(無(wú)參數(shù)有返回值)。在打代碼時(shí)根據(jù)需求選擇對(duì)應(yīng)的任務(wù)類(lèi)型。
1.1 實(shí)現(xiàn)Runnable接口的類(lèi)
多線程任務(wù)類(lèi)型,首先自然想到的就是實(shí)現(xiàn) Runnable 接口的類(lèi),Runnable接口提供了一個(gè)抽象方法run,這個(gè)方法無(wú)參數(shù),無(wú)返回值。例如:
Runnable task = new Runnable() { @Override public void run() { System.out.println("Execute task."); } };
或者Java 8 及以上版本更簡(jiǎn)單的寫(xiě)法:
Runnable task = ()->{ System.out.println("Execute task."); };
1.2 實(shí)現(xiàn)Callable接口的類(lèi)
于Runnable一樣Callable也只有一個(gè)抽象方法,不過(guò)該抽象方法有返回值。在實(shí)現(xiàn)該接口的時(shí)候需要制定返回值的類(lèi)型。例如:
Callable<String> callableTask = ()-> "finished";
2 線程池類(lèi)型
java.util.concurrent.Executors 提供了一系列靜態(tài)方法來(lái)創(chuàng)建各種線程池。下面例舉出了主要的一些線程池及特性,其它未例舉線程池的特性可由下面這些推導(dǎo)出來(lái)。
2.1 線程數(shù)固定的線程池 Fixed Thread Pool
顧名思義,這種類(lèi)型線程池線程數(shù)量是固定的。如果線程數(shù)量設(shè)置為n,則任何時(shí)刻該線程池最多只有n個(gè)線程處于運(yùn)行狀態(tài)。當(dāng)線程池中處于飽和運(yùn)行狀態(tài)時(shí),再往線程池中提交的任務(wù)會(huì)被放到執(zhí)行隊(duì)列中。如果線程池處于不飽和狀態(tài),線程池也會(huì)一直存在,直到ExecuteService 的shutdown方法被調(diào)用,線程池才會(huì)被清除。
// 創(chuàng)建線程數(shù)量為5的線程池。 ExecutorService executorService = Executors.newFixedThreadPool(5);
2.2 可緩存的線程池 Cached Thread Pool
這種類(lèi)型的線程池初始大小為0個(gè)線程,隨著往池里不斷提交任務(wù),如果線程池里面沒(méi)有閑置線程(0個(gè)線程也表示沒(méi)有閑置線程),則會(huì)創(chuàng)建新的線程,保證沒(méi)有任務(wù)在等待;如果有閑置線程,則復(fù)用閑置狀態(tài)線程執(zhí)行任務(wù)。處于閑置狀態(tài)的線程只會(huì)在線程池中緩存60秒,閑置時(shí)間達(dá)到60s的線程會(huì)被關(guān)閉并移出線程池。在處理大量短暫的(官方說(shuō)法:short-lived)異步任務(wù)時(shí)可以顯著得提供程序性能。
//創(chuàng)建一個(gè)可緩存的線程池 ExecutorService executorService = Executors.newCachedThreadPool();
2.3 單線程池
這或許不能叫線程池了,由于它里面的線程永遠(yuǎn)只有1個(gè),而且自始至終都只有1個(gè)(為什么說(shuō)這句話,因?yàn)橐?nbsp;Executors.newFixedThreadPool(1) 區(qū)別開(kāi)來(lái)),所以還是叫它“單線程池把”。你盡可以往單線程池中添加任務(wù),但是每次只執(zhí)行1個(gè),且任務(wù)是按順序執(zhí)行的。如果前面的任務(wù)出現(xiàn)了異常,當(dāng)前線程會(huì)被銷(xiāo)毀,但1個(gè)新的線程會(huì)被創(chuàng)建用來(lái)執(zhí)行后面的任務(wù)。以上這些和線程數(shù)只有1個(gè)的線程Fixed Thread Pool一樣。兩者唯一不同的是, Executors.newFixedThreadPool(1) 可以在運(yùn)行時(shí)修改它里面的線程數(shù),而 Executors.newSingleThreadExecutor() 永遠(yuǎn)只能有1個(gè)線程。
//創(chuàng)建一個(gè)單線程池 ExecutorService executorService = Executors.newSingleThreadExecutor();
2.4 工作竊取線程池
扒開(kāi)源碼,會(huì)發(fā)現(xiàn)工作竊取線程池本質(zhì)是 ForkJoinPool ,這類(lèi)線程池充分利用CPU多核處理任務(wù),適合處理消耗CPU資源多的任務(wù)。它的線程數(shù)不固定,維護(hù)的任務(wù)隊(duì)列有多個(gè),當(dāng)一個(gè)任務(wù)隊(duì)列完成時(shí),相應(yīng)的線程會(huì)從其它的任務(wù)隊(duì)列中竊取任務(wù)執(zhí)行,這也意味著任務(wù)的開(kāi)始執(zhí)行順序并和提交順序相同。如果有更高的需求,可以直接通過(guò)ForkJoinPool獲取線程池。
//創(chuàng)建一個(gè)工作竊取線程池,使用CPU核數(shù)等于機(jī)器的CPU核數(shù) ExecutorService executorService = Executors.newWorkStealingPool(); //創(chuàng)建一個(gè)工作竊取線程池,使用CPU 3 個(gè)核進(jìn)行計(jì)算,工作竊取線程池不能設(shè)置線程數(shù) ExecutorService executorService2 = Executors.newWorkStealingPool(3);
2.5 計(jì)劃任務(wù)線程池
計(jì)劃任務(wù)線程池可以按計(jì)劃執(zhí)行某些任務(wù),例如:周期性的執(zhí)行某項(xiàng)任務(wù)。
// 獲取一個(gè)大小為2的計(jì)劃任務(wù)線程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); // 添加一個(gè)打印當(dāng)前線程信息計(jì)劃任務(wù),該任務(wù)在3秒后執(zhí)行 scheduledExecutorService.schedule(() -> { System.out.println(Thread.currentThread()); }, 3, TimeUnit.SECONDS); // 添加一個(gè)打印當(dāng)前線程信息計(jì)劃任務(wù),該任務(wù)在2秒后首次執(zhí)行,之后每5秒執(zhí)行一次。如果任務(wù)執(zhí)行時(shí)間超過(guò)了5秒,則下一次將會(huì)在前一次執(zhí)行完成之后立即執(zhí)行 scheduledExecutorService.scheduleAtFixedRate(() -> { System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS); // 添加一個(gè)打印當(dāng)前線程信息計(jì)劃任務(wù),該任務(wù)在2秒后首次執(zhí)行,之后每次在任務(wù)執(zhí)行之后5秒執(zhí)行下一次。 scheduledExecutorService.scheduleWithFixedDelay(() -> { System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS); // 逐個(gè)清除 idle 狀態(tài)的線程 scheduledExecutorService.shutdown(); // 阻塞,在線程池被關(guān)調(diào)之前代碼不再往下走 scheduledExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
3 使用線程池執(zhí)行任務(wù)
前面提到,任務(wù)類(lèi)型分為有返回值和無(wú)返回值的類(lèi)型,這里的調(diào)用也分為有返回值調(diào)用和無(wú)返回值的調(diào)用。
3.1 無(wú)返回值任務(wù)的調(diào)用
如果是無(wú)返回值任務(wù)的調(diào)用,可以用execute或者submit方法,這種情況下二者本質(zhì)上一樣。為了于有返回值任務(wù)調(diào)用保持統(tǒng)一,建議采用submit方法。
//創(chuàng)建一個(gè)線程池 ExecutorService executorService = Executors.newFixedThreadPool(3); //提交一個(gè)無(wú)返回值的任務(wù)(實(shí)現(xiàn)了Runnable接口) executorService.submit(()->System.out.println("Hello")); executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
如果有一個(gè)任務(wù)集合,可以一個(gè)個(gè)提交。
//創(chuàng)建一個(gè)線程池 ExecutorService executorService = Executors.newFixedThreadPool(3); List<Runnable> tasks = Arrays.asList( ()->System.out.println("Hello"), ()->System.out.println("World")); //逐個(gè)提交任務(wù) tasks.forEach(executorService::submit); executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
3.2 有返回值任務(wù)的調(diào)用
有返回值的任務(wù)需要實(shí)現(xiàn)Callable接口,實(shí)現(xiàn)的時(shí)候在泛型位置指定返回值類(lèi)型。在調(diào)用submit方法時(shí)會(huì)返回一個(gè)Future對(duì)象,通過(guò)Future的方法get()可以拿到返回值。這里需要注意的是,調(diào)用get()時(shí)代碼會(huì)阻塞,直到任務(wù)完成,有返回值。
ExecutorService executorService = Executors.newFixedThreadPool(2); Future<String> future = executorService.submit(()->"Hello"); System.out.println(future.isDone());//false String value = future.get(); System.out.println(future.isDone());//true System.out.println(value);//Hello
如果要提交一批任務(wù),ExecutorService除了可以逐個(gè)提交之外,還可以調(diào)用invokeAll一次性提交,invokeAll的內(nèi)部實(shí)現(xiàn)其實(shí)就是用一個(gè)循環(huán)逐個(gè)提交任務(wù)。invokeAll返回的值是一個(gè)Future List。
ExecutorService executorService = Executors.newFixedThreadPool(2); List<Callable<String>> tasks = Arrays.asList(()->"Hello", ()->"World"); List<Future<String>> futures = executorService.invokeAll(tasks);
invokeAny方法也很有用,線程池執(zhí)行若干個(gè)實(shí)現(xiàn)了Callable的任務(wù),然后返回最先執(zhí)行結(jié)束的任務(wù)的值,其它未完成的任務(wù)將被正常取消掉不會(huì)有異常。如下代碼不會(huì)輸出“Hello”
ExecutorService executorService = Executors.newFixedThreadPool(2); List<Callable<String>> tasks = Arrays.asList( () -> { Thread.sleep(500L); System.out.println("Hello"); return "Hello"; }, () -> { System.out.println("World"); return "World"; }); String s = executorService.invokeAny(tasks); System.out.println(s);//World
輸出:
World World
另外,在查看ExecutorService源碼時(shí)發(fā)現(xiàn)它還提供了一個(gè)方法 <T> Future<T> submit(Runnable task, T result);
,可以通過(guò)這個(gè)方法提交一個(gè)實(shí)現(xiàn)了Runnable接口的任務(wù),然后有返回值,而Runnable接口中的run方法時(shí)沒(méi)有返回值的。那它的返回值是哪來(lái)的呢?其實(shí)問(wèn)題在于該submit方法后面的一個(gè)參數(shù),這個(gè)參數(shù)值就是返回的值。調(diào)用submit方法之后,有一通操作,然后直接把result參數(shù)返回了。
ExecutorService executorService = Executors.newFixedThreadPool(1); Future<String> future = executorService.submit(() -> System.out.println("Hello"), "World"); System.out.println(future.get());//輸出:World
4 小結(jié)
在利用多線程處理任務(wù)時(shí),應(yīng)該根據(jù)情況選擇合適的任務(wù)類(lèi)型和線程池類(lèi)型。如果無(wú)返回值,可以采用實(shí)現(xiàn)Runnable或Callable接口的任務(wù);如果有返回值,應(yīng)該使用實(shí)現(xiàn)Callable接口的任務(wù),返回值通過(guò)Future的get方法取到。選用線程池時(shí),如果只用1個(gè)線程,用單線程池或者容量為1的固定容量線程池;處理大量short-live任務(wù)是,使用可緩存的線程池;若要有計(jì)劃或者循環(huán)執(zhí)行某些任務(wù),可以采用計(jì)劃任務(wù)線程池;如果任務(wù)需要消耗大量的CPU資源,應(yīng)用工作竊取線程池。
以上就是Java 使用線程池執(zhí)行多個(gè)任務(wù)的示例的詳細(xì)內(nèi)容,更多關(guān)于Java 線程池執(zhí)行任務(wù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java枚舉類(lèi)的屬性、方法和構(gòu)造方法應(yīng)用實(shí)戰(zhàn)
這篇文章主要介紹了java枚舉類(lèi)的屬性、方法和構(gòu)造方法應(yīng)用,結(jié)合實(shí)例形式分析了java枚舉類(lèi)的定義、構(gòu)造及相關(guān)應(yīng)用操作技巧,需要的朋友可以參考下2019-08-08SpringBoot配置Spring?Security的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot配置Spring?Security的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-10-10如何使用Spring工具類(lèi)動(dòng)態(tài)匹配url
這篇文章主要介紹了如何使用Spring工具類(lèi)動(dòng)態(tài)匹配url,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12簡(jiǎn)單捋捋@RequestParam 和 @RequestBody的使用
這篇文章主要介紹了簡(jiǎn)單捋捋@RequestParam 和 @RequestBody的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12mybatis 解決從列名到屬性名的自動(dòng)映射失敗問(wèn)題
這篇文章主要介紹了mybatis 解決從列名到屬性名的自動(dòng)映射失敗問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06SpringBoot2底層注解@ConfigurationProperties配置綁定
這篇文章主要介紹了SpringBoot2底層注解@ConfigurationProperties配置綁定,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Java GUI編程之貪吃蛇游戲簡(jiǎn)單實(shí)現(xiàn)方法【附demo源碼下載】
這篇文章主要介紹了Java GUI編程之貪吃蛇游戲簡(jiǎn)單實(shí)現(xiàn)方法,詳細(xì)分析了貪吃蛇游戲的具體實(shí)現(xiàn)步驟與相關(guān)注意事項(xiàng),并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2017-09-09