@Async異步線程池以及線程的命名方式
本文記錄@Async的基本使用以及通過實(shí)現(xiàn)ThreadFactory來實(shí)現(xiàn)對線程的命名。
@Async的基本使用
近日有一個道友提出到一個問題,大意如下:
業(yè)務(wù)場景需要進(jìn)行批量更新,已有數(shù)據(jù)id主鍵、更新的狀態(tài)。單條更新性能太慢,所以使用in進(jìn)行批量更新。但是會導(dǎo)致鎖表使得其他業(yè)務(wù)無法訪問該表,in的量級太低又導(dǎo)致性能太慢。
龍道友提出了一個解決方案,把要處理的數(shù)據(jù)分成幾個list之后使用多線程進(jìn)行數(shù)據(jù)更新。提到多線程可直接使用@Async注解來進(jìn)行異步操作。
好的,接下來上面的問題我們不予解答,來說下@Async的簡單使用
@Async在SpringBoot中的使用較為簡單,所在位置如下:
一.啟動類加入注解@EnableAsync
第一步就是在啟動類中加入@EnableAsync注解 啟動類代碼如下:
@SpringBootApplication @EnableAsync public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
二.編寫MyAsyncConfigurer類
MyAsyncConfigurer類實(shí)現(xiàn)了AsyncConfigurer接口,重寫AsyncConfigurer接口的兩個重要方法:
1.getAsyncExecutor:自定義線程池,若不重寫會使用默認(rèn)的線程池。
2.getAsyncUncaughtExceptionHandler:捕捉IllegalArgumentException異常.
一方法很好理解。二方法中提到的IllegalArgumentException異常在之后會說明。代碼如下:
/** * @author hsw * @Date 20:12 2018/8/23 */ @Slf4j @Component public class MyAsyncConfigurer implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { //定義一個最大為10個線程數(shù)量的線程池 ExecutorService service = Executors.newFixedThreadPool(10); return service; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new MyAsyncExceptionHandler(); } class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { log.info("Exception message - " + throwable.getMessage()); log.info("Method name - " + method.getName()); for (Object param : objects) { log.info("Parameter value - " + param); } } } }</code>
三.三種測試方法
1.無參無返回值方法
2.有參無返回值方法
3.有參有返回值方法
具體代碼如下:
/** * @author hsw * @Date 20:07 2018/8/23 */ @Slf4j @Component public class AsyncExceptionDemo { @Async public void simple() { log.info("this is a void method"); } @Async public void inputDemo (String s) { log.info("this is a input method,{}",s); throw new IllegalArgumentException("inputError"); } @Async public Future hardDemo (String s) { log.info("this is a hard method,{}",s); Future future; try { Thread.sleep(3000); throw new IllegalArgumentException(); }catch (InterruptedException e){ future = new AsyncResult("InterruptedException error"); }catch (IllegalArgumentException e){ future = new AsyncResult("i am throw IllegalArgumentException error"); } return future; } }
在第二種方法中,拋出了一種名為IllegalArgumentException的異常,在上述第二步中,我們已經(jīng)通過重寫getAsyncUncaughtExceptionHandler方法,完成了對產(chǎn)生該異常時的處理。
在處理方法中我們簡單列出了異常的各個信息。
ok,現(xiàn)在該做的準(zhǔn)備工作都已完成,加個測試方法看看結(jié)果如何
/** * @author hsw * @Date 20:16 2018/8/23 */ @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class AsyncExceptionDemoTest { @Autowired private AsyncExceptionDemo asyncExceptionDemo; @Test public void simple() { for (int i=0;i<3;i++){ try { asyncExceptionDemo.simple(); asyncExceptionDemo.inputDemo("input"); Future future = asyncExceptionDemo.hardDemo("hard"); log.info(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
測試結(jié)果如下:
2018-08-25 16:25:03.856 INFO 4396 --- [ main] c.hsw.test.async.AsyncExceptionDemoTest : Started AsyncExceptionDemoTest in 3.315 seconds (JVM running for 4.184)
2018-08-25 16:25:03.947 INFO 4396 --- [pool-1-thread-1] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:25:03.947 INFO 4396 --- [pool-1-thread-3] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:25:03.947 INFO 4396 --- [pool-1-thread-2] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:25:03.947 INFO 4396 --- [pool-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Exception message - inputError
2018-08-25 16:25:03.947 INFO 4396 --- [pool-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Method name - inputDemo
2018-08-25 16:25:03.947 INFO 4396 --- [pool-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Parameter value - input
2018-08-25 16:25:06.947 INFO 4396 --- [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:25:06.948 INFO 4396 --- [pool-1-thread-4] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:25:06.948 INFO 4396 --- [pool-1-thread-6] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:25:06.948 INFO 4396 --- [pool-1-thread-5] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:25:06.948 INFO 4396 --- [pool-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Exception message - inputError
2018-08-25 16:25:06.948 INFO 4396 --- [pool-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Method name - inputDemo
2018-08-25 16:25:06.948 INFO 4396 --- [pool-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Parameter value - input
2018-08-25 16:25:09.949 INFO 4396 --- [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:25:09.950 INFO 4396 --- [pool-1-thread-7] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:25:09.950 INFO 4396 --- [pool-1-thread-8] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:25:09.950 INFO 4396 --- [pool-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Exception message - inputError
2018-08-25 16:25:09.950 INFO 4396 --- [pool-1-thread-9] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:25:09.950 INFO 4396 --- [pool-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Method name - inputDemo
2018-08-25 16:25:09.950 INFO 4396 --- [pool-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Parameter value - input
2018-08-25 16:25:12.950 INFO 4396 --- [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:25:12.953 INFO 4396 --- [ Thread-2] o.s.w.c.s.GenericWebApplicationContext : Closing org.springframework.web.context.support.GenericWebApplicationContext@71075444: startup date [Sat Aug 25 16:25:01 CST 2018]; root of context hierarchy
測試成功。
到此為止,關(guān)于@Async注解的基本使用內(nèi)容結(jié)束。但是在測試過程中,我注意到線程名的命名為默認(rèn)的pool-1-thread-X,雖然可以分辨出不同的線程在進(jìn)行作業(yè),但依然很不方便,為什么默認(rèn)會以這個命名方式進(jìn)行命名呢?
線程池的命名
點(diǎn)進(jìn)Executors.newFixedThreadPool,發(fā)現(xiàn)在初始化線程池的時候有另一種方法
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
那么這個ThreadFactory為何方神圣?繼續(xù)ctrl往里點(diǎn),在類中發(fā)現(xiàn)這么一個實(shí)現(xiàn)類。
破案了破案了。原來是在這里對線程池進(jìn)行了命名。那我們只需自己實(shí)現(xiàn)ThreadFactory接口,對命名方法進(jìn)行重寫即可,完成代碼如下:
static class MyNamedThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; MyNamedThreadFactory(String name) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); if (null == name || name.isEmpty()) { name = "pool"; } namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } }
然后在創(chuàng)建線程池的時候加上新寫的類即可對線程名進(jìn)行自定義。
@Override public Executor getAsyncExecutor() { ExecutorService service = Executors.newFixedThreadPool(10,new MyNamedThreadFactory("HSW")); return service; }
看看重命名后的執(zhí)行結(jié)果。
2018-08-25 16:42:44.068 INFO 46028 --- [ main] c.hsw.test.async.AsyncExceptionDemoTest : Started AsyncExceptionDemoTest in 3.038 seconds (JVM running for 3.83)
2018-08-25 16:42:44.155 INFO 46028 --- [ HSW-1-thread-3] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:42:44.155 INFO 46028 --- [ HSW-1-thread-2] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:42:44.155 INFO 46028 --- [ HSW-1-thread-1] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:42:44.155 INFO 46028 --- [ HSW-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Exception message - inputError
2018-08-25 16:42:44.155 INFO 46028 --- [ HSW-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Method name - inputDemo
2018-08-25 16:42:44.155 INFO 46028 --- [ HSW-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Parameter value - input
2018-08-25 16:42:47.155 INFO 46028 --- [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:42:47.156 INFO 46028 --- [ HSW-1-thread-4] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:42:47.156 INFO 46028 --- [ HSW-1-thread-5] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:42:47.156 INFO 46028 --- [ HSW-1-thread-6] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:42:47.156 INFO 46028 --- [ HSW-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Exception message - inputError
2018-08-25 16:42:47.156 INFO 46028 --- [ HSW-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Method name - inputDemo
2018-08-25 16:42:47.156 INFO 46028 --- [ HSW-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Parameter value - input
2018-08-25 16:42:50.156 INFO 46028 --- [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:42:50.157 INFO 46028 --- [ HSW-1-thread-7] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:42:50.157 INFO 46028 --- [ HSW-1-thread-8] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:42:50.157 INFO 46028 --- [ HSW-1-thread-9] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:42:50.157 INFO 46028 --- [ HSW-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Exception message - inputError
2018-08-25 16:42:50.157 INFO 46028 --- [ HSW-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Method name - inputDemo
2018-08-25 16:42:50.157 INFO 46028 --- [ HSW-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Parameter value - input
2018-08-25 16:42:53.157 INFO 46028 --- [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:42:53.161 INFO 46028 --- [ Thread-2] o.s.w.c.s.GenericWebApplicationContext : Closing org.springframework.web.context.support.GenericWebApplicationContext@71075444: startup date [Sat Aug 25 16:42:41 CST 2018]; root of context hierarchy
Spring Boot使用@Async實(shí)現(xiàn)異步調(diào)用:自定義線程池
定義線程池
第一步,先在Spring Boot主類中定義一個線程池,比如:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @EnableAsync @Configuration class TaskPoolConfig { @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(200); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("taskExecutor-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } } }
上面我們通過使用ThreadPoolTaskExecutor創(chuàng)建了一個線程池,同時設(shè)置了以下這些參數(shù):
- 核心線程數(shù)10:線程池創(chuàng)建時候初始化的線程數(shù)
- 最大線程數(shù)20:線程池最大的線程數(shù),只有在緩沖隊列滿了之后才會申請超過核心線程數(shù)的線程
- 緩沖隊列200:用來緩沖執(zhí)行任務(wù)的隊列
- 允許線程的空閑時間60秒:當(dāng)超過了核心線程出之外的線程在空閑時間到達(dá)之后會被銷毀
- 線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池
- 線程池對拒絕任務(wù)的處理策略:這里采用了CallerRunsPolicy策略,當(dāng)線程池沒有處理能力的時候,該策略會直接在 execute 方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù);如果執(zhí)行程序已關(guān)閉,則會丟棄該任務(wù)
使用線程池
在定義了線程池之后,我們?nèi)绾巫尞惒秸{(diào)用的執(zhí)行任務(wù)使用這個線程池中的資源來運(yùn)行呢?方法非常簡單,我們只需要在@Async注解中指定線程池名即可,比如:
@Slf4j @Component public class Task { public static Random random = new Random(); @Async("taskExecutor") public void doTaskOne() throws Exception { log.info("開始做任務(wù)一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("完成任務(wù)一,耗時:" + (end - start) + "毫秒"); } @Async("taskExecutor") public void doTaskTwo() throws Exception { log.info("開始做任務(wù)二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("完成任務(wù)二,耗時:" + (end - start) + "毫秒"); } @Async("taskExecutor") public void doTaskThree() throws Exception { log.info("開始做任務(wù)三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("完成任務(wù)三,耗時:" + (end - start) + "毫秒"); } }
單元測試
最后,我們來寫個單元測試來驗(yàn)證一下
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class ApplicationTests { @Autowired private Task task; @Test public void test() throws Exception { task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); Thread.currentThread().join(); } }
執(zhí)行上面的單元測試,我們可以在控制臺中看到所有輸出的線程名前都是之前我們定義的線程池前綴名開始的,說明我們使用線程池來執(zhí)行異步任務(wù)的試驗(yàn)成功了!
2018-03-27 22:01:15.620 INFO 73703 --- [ taskExecutor-1] com.didispace.async.Task : 開始做任務(wù)一
2018-03-27 22:01:15.620 INFO 73703 --- [ taskExecutor-2] com.didispace.async.Task : 開始做任務(wù)二
2018-03-27 22:01:15.620 INFO 73703 --- [ taskExecutor-3] com.didispace.async.Task : 開始做任務(wù)三
2018-03-27 22:01:18.165 INFO 73703 --- [ taskExecutor-2] com.didispace.async.Task : 完成任務(wù)二,耗時:2545毫秒
2018-03-27 22:01:22.149 INFO 73703 --- [ taskExecutor-3] com.didispace.async.Task : 完成任務(wù)三,耗時:6529毫秒
2018-03-27 22:01:23.912 INFO 73703 --- [ taskExecutor-1] com.didispace.async.Task : 完成任務(wù)一,耗時:8292毫秒
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合Mybatis-Plus、Jwt實(shí)現(xiàn)登錄token設(shè)置
Spring Boot整合Mybatis-plus實(shí)現(xiàn)登錄常常需要使用JWT來生成用戶的token并設(shè)置用戶權(quán)限的攔截器,本文就來詳細(xì)的介紹一下,具有一定的參考價值,感興趣的可以了解一下2024-02-02MybatisPlus多數(shù)據(jù)源及事務(wù)解決思路
這篇文章主要介紹了MybatisPlus多數(shù)據(jù)源及事務(wù)解決思路,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01Java使用Fastjson進(jìn)行JSON數(shù)據(jù)操作教程詳解
Fastjson?是一個?Java?庫,可以用來將?Java?對象轉(zhuǎn)換為它們的?JSON?表示,本文主要為大家詳細(xì)介紹了Java如何使用Fastjson進(jìn)行JSON數(shù)據(jù)操作,需要的可以參考下2023-12-12Mybatis如何實(shí)現(xiàn)InsertOrUpdate功能
這篇文章主要介紹了Mybatis如何實(shí)現(xiàn)InsertOrUpdate功能,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05SpringBoot登錄、退出、獲取用戶信息的session處理方案
這篇文章主要介紹了SpringBoot登錄、退出、獲取用戶信息的session處理,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08使用@PathVariable注解如何實(shí)現(xiàn)動態(tài)傳值
這篇文章主要介紹了使用@PathVariable注解如何實(shí)現(xiàn)動態(tài)傳值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10