詳解java開(kāi)啟異步線程的幾種方法(@Async,AsyncManager,線程池)
整體描述
在java中異步線程很重要,比如在業(yè)務(wù)流處理時(shí),需要通知硬件設(shè)備,發(fā)短信通知用戶,或者需要上傳一些圖片資源到其他服務(wù)器這種耗時(shí)的操作,在主線程里處理會(huì)阻塞整理流程,而且我們也不需要等待處理結(jié)果之后再進(jìn)行下一步操作,這時(shí)候就可以使用異步線程進(jìn)行處理,這樣主線程不會(huì)因?yàn)檫@些耗時(shí)的操作而阻塞,保證主線程的流程可以正常進(jìn)行。最近在項(xiàng)目中使用了很多線程的操作,在這做個(gè)記錄。
實(shí)現(xiàn)方法
線程的操作,是java中最重要的部分之一,實(shí)現(xiàn)線程操作也有很多種方法,這里僅介紹幾種常用的。在springboot框架中,可以使用注解簡(jiǎn)單實(shí)現(xiàn)線程的操作,還有AsyncManager的方式,如果需要復(fù)雜的線程操作,可以使用線程池實(shí)現(xiàn)。下面根據(jù)具體方法進(jìn)行介紹。
一、注解@Async
springboot框架的注解,使用時(shí)也有一些限制,這個(gè)在網(wǎng)上也有很多介紹,@Async注解不能在類(lèi)本身直接調(diào)用,在springboot框架中,可以使用單獨(dú)的Service實(shí)現(xiàn)異步方法,然后在其他的類(lèi)中調(diào)用該Service中的異步方法即可,具體如下:
1. 添加注解
在springboot的config中添加 @EnableAsync注解,開(kāi)啟異步線程功能
package com.thcb.boot.config; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; /** * MyConfig * * @author thcb */ @Configuration @EnableAsync public class MyConfig { // 自己配置的Config }
2. 創(chuàng)建異步方法Service和實(shí)現(xiàn)類(lèi)
使用service實(shí)現(xiàn)耗時(shí)的方法
Service類(lèi):
package com.thcb.execute.service; import org.springframework.scheduling.annotation.Async; /** * IExecuteService * * @author thcb */ public interface IExecuteService { /** * 一些耗時(shí)的操作,使用單獨(dú)線程處理 * 這里就簡(jiǎn)單寫(xiě)了一個(gè)sleep5秒的操作 */ @Async public void sleepingTest(); }
Service實(shí)現(xiàn)類(lèi):
package com.thcb.execute.service.impl; import com.thcb.execute.service.IExecuteService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; /** * ExecuteService業(yè)務(wù)層處理 * * @author thcb */ @Service public class ExecuteServiceImpl implements IExecuteService { private static final Logger log = LoggerFactory.getLogger(ExecuteServiceImpl.class); @Override public void sleepingTest() { log.info("SleepingTest start"); try { Thread.sleep(5000); } catch (Exception e) { log.error("SleepingTest:" + e.toString()); } log.info("SleepingTest end"); } }
3. 調(diào)用異步方法
這里根據(jù)Springboot的框架,在controller層調(diào)用,并使用log查看是否時(shí)異步結(jié)果。
controller:
package com.thcb.boot.controller; import com.thcb.execute.service.IExecuteService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * TestController * * @author thcb */ @RestController public class TestController { private static final Logger log = LoggerFactory.getLogger(TestController.class); @Autowired private IExecuteService executeService; @RequestMapping("/test") public String test() { return "spring boot"; } @RequestMapping("/executeTask") public String executeTask() { log.info("executeTask Start!"); executeService.sleepingTest(); log.info("executeTask End!"); return "executeTask"; } }
在log查看結(jié)果:
接口直接返回了executeTask,并log出executeTask End!在5秒之后,log打出SleepingTest end,說(shuō)明使用了異步線程處理了executeService.sleepingTest的方法。
二、AsyncManager
使用AsyncManager方法,也是SpringBoot框架中帶的任務(wù)管理器,可以實(shí)現(xiàn)異步線程。
1. 創(chuàng)建AsyncManager類(lèi)
使用AsyncManager首先需要?jiǎng)?chuàng)建一個(gè)AsyncManager類(lèi),這個(gè)在springboot框架中應(yīng)該也是有的:
/** * 異步任務(wù)管理器 * * @author thcb */ public class AsyncManager { /** * 操作延遲10毫秒 */ private final int OPERATE_DELAY_TIME = 10; /** * 異步操作任務(wù)調(diào)度線程池 */ private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); /** * 單例模式 */ private AsyncManager() { } private static AsyncManager me = new AsyncManager(); public static AsyncManager me() { return me; } /** * 執(zhí)行任務(wù) * * @param task 任務(wù) */ public void execute(TimerTask task) { executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); } /** * 停止任務(wù)線程池 */ public void shutdown() { Threads.shutdownAndAwaitTermination(executor); } }
2. 創(chuàng)建一個(gè)耗時(shí)的操作類(lèi)
這里同樣需要?jiǎng)?chuàng)建一個(gè)耗時(shí)的操作,也是用sleep模擬:
public TimerTask sleepingTest() { return new TimerTask() { @Override public void run() { // 耗時(shí)操作 try { Thread.sleep(5000); } catch (Exception e) { log.error("SleepingTest:" + e.toString()); } } }; }
3. 執(zhí)行異步操作
使用AsyncManager執(zhí)行異步操作也比較簡(jiǎn)單,直接調(diào)用即可:
// 異步線程池 AsyncManager.me().execute(sleepingTest());
三、線程池
使用線程池可以設(shè)定更多的參數(shù),線程池在網(wǎng)上也有很多詳細(xì)的介紹,在這我只介紹一種,帶拒絕策略的線程池。
1. 創(chuàng)建線程池
創(chuàng)建帶有拒絕策略的線程池,并設(shè)定核心線程數(shù),最大線程數(shù),隊(duì)列數(shù)和超出核心線程數(shù)量的線程存活時(shí)間:
/** * 線程池信息: 核心線程數(shù)量5,最大數(shù)量10,隊(duì)列大小20,超出核心線程數(shù)量的線程存活時(shí)間:30秒, 指定拒絕策略的 */ private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(20), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { log.error("有任務(wù)被拒絕執(zhí)行了"); } });
2. 創(chuàng)建一個(gè)耗時(shí)的操作類(lèi)
由于線程池需要傳入一個(gè)Runnable,所以此類(lèi)繼承Runnable,還是用sleep模擬耗時(shí)操作。
/** * 耗時(shí)操作 */ static class MyTask implements Runnable { private int taskNum; public MyTask(int num) { this.taskNum = num; } @Override public void run() { System.out.println("正在執(zhí)行task " + taskNum); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task " + taskNum + "執(zhí)行完畢"); } }
3. 執(zhí)行線程池
開(kāi)啟線程池,這里通過(guò)一個(gè)for循環(huán)模擬一下,可以看一下log輸出,有興趣的可以修改一下for循環(huán)和sleep的數(shù)值,看看線程池具體的操作和拒絕流程。
for (int i = 0; i < 20; i++) { MyTask myTask = new MyTask(i); threadPoolExecutor.execute(myTask); System.out.println("線程池中線程數(shù)目:" + threadPoolExecutor.getPoolSize() + ",隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:" + threadPoolExecutor.getQueue().size() + ",已執(zhí)行完別的任務(wù)數(shù)目:" + threadPoolExecutor.getCompletedTaskCount()); } threadPoolExecutor.shutdown();
總結(jié)
在此寫(xiě)一些線程操作需要注意的地方:
- 線程數(shù)量和cpu有關(guān),使用線程時(shí)一定要注意線程的釋放,否則會(huì)導(dǎo)致cpu線程數(shù)量耗盡;
- 使用注解完成的線程操作,不可以在自己的類(lèi)中實(shí)現(xiàn)調(diào)用,因?yàn)樽⒔庾詈笠彩峭ㄟ^(guò)代理的方式完成異步線程的,最好時(shí)在單獨(dú)的一個(gè)service中寫(xiě);
- 線程池最好單獨(dú)寫(xiě),使用static和final修飾,保證所有使用該線程池的地方使用的是一個(gè)線程池,而不能每次都new一個(gè)線程池出來(lái),每次都new一個(gè)就沒(méi)有意義了。
以上就是三種線程池的操作,寫(xiě)的不算很詳細(xì),有興趣的同學(xué)可以自己在深入研究一下,還有Java8新加的CompletableFuture,可以單獨(dú)寫(xiě)一篇文章了,在此篇就不再介紹了:)
到此這篇關(guān)于java開(kāi)啟異步線程的幾種方法(@Async,AsyncManager,線程池)的文章就介紹到這了,更多相關(guān)java開(kāi)啟異步線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java編程幾個(gè)循環(huán)實(shí)例代碼分享
這篇文章主要介紹了Java編程幾個(gè)循環(huán)實(shí)例代碼分享,多看多練,小編覺(jué)得還是挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-10-10Java如何通過(guò)Maven管理項(xiàng)目依賴(lài)
這篇文章主要介紹了Java如何通過(guò)Maven管理項(xiàng)目依賴(lài),幫助大家更好的理解和使用maven,感興趣的朋友可以了解下2020-10-10Java使用過(guò)濾器防止SQL注入XSS腳本注入的實(shí)現(xiàn)
這篇文章主要介紹了Java使用過(guò)濾器防止SQL注入XSS腳本注入,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Java 8函數(shù)式接口Function BiFunction DoubleFunction
這篇文章主要為大家介紹了Java 8函數(shù)式接口Function BiFunction DoubleFunction區(qū)別示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07JavaSwing實(shí)現(xiàn)小型學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了JavaSwing實(shí)現(xiàn)小型學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02Java實(shí)現(xiàn)根據(jù)模板讀取PDF并替換指定內(nèi)容
在實(shí)際開(kāi)發(fā)里,經(jīng)常會(huì)遇到需要根據(jù)?PDF?模板文檔生成特定?PDF?的需求,本文將利用Java中的iText實(shí)現(xiàn)讀取?PDF?模板文檔并替換指定內(nèi)容,最后重新生成新PDF,感興趣的可以了解下2025-02-02java必學(xué)必會(huì)之網(wǎng)絡(luò)編程
java必學(xué)必會(huì)之網(wǎng)絡(luò)編程,學(xué)習(xí)了解java網(wǎng)絡(luò)編程、網(wǎng)絡(luò)通信協(xié)議、TCP協(xié)議和UDP協(xié)議,對(duì)各個(gè)協(xié)議進(jìn)行深入學(xué)習(xí),做到必學(xué)必會(huì)2015-12-12java8實(shí)現(xiàn)List中對(duì)象屬性的去重方法
這篇文章主要介紹了java8實(shí)現(xiàn)List中對(duì)象屬性的去重方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03使用mybatis的@Interceptor實(shí)現(xiàn)攔截sql的方法詳解
攔截器是一種基于 AOP(面向切面編程)的技術(shù),它可以在目標(biāo)對(duì)象的方法執(zhí)行前后插入自定義的邏輯,本文給大家介紹了使用mybatis的@Interceptor實(shí)現(xiàn)攔截sql的方法,需要的朋友可以參考下2024-03-03