@Async异步线程池以及线程的命名方式
作者:hhsway
本文记录@Async的基本使用以及通过实现ThreadFactory来实现对线程的命名。
@Async的基本使用
近日有一个道友提出到一个问题,大意如下:
业务场景需要进行批量更新,已有数据id主键、更新的状态。单条更新性能太慢,所以使用in进行批量更新。但是会导致锁表使得其他业务无法访问该表,in的量级太低又导致性能太慢。
龙道友提出了一个解决方案,把要处理的数据分成几个list之后使用多线程进行数据更新。提到多线程可直接使用@Async注解来进行异步操作。
好的,接下来上面的问题我们不予解答,来说下@Async的简单使用
@Async在SpringBoot中的使用较为简单,所在位置如下:
一.启动类加入注解@EnableAsync
第一步就是在启动类中加入@EnableAsync注解 启动类代码如下:
@SpringBootApplication @EnableAsync public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
二.编写MyAsyncConfigurer类
MyAsyncConfigurer类实现了AsyncConfigurer接口,重写AsyncConfigurer接口的两个重要方法:
1.getAsyncExecutor:自定义线程池,若不重写会使用默认的线程池。
2.getAsyncUncaughtExceptionHandler:捕捉IllegalArgumentException异常.
一方法很好理解。二方法中提到的IllegalArgumentException异常在之后会说明。代码如下:
/** * @author hsw * @Date 20:12 2018/8/23 */ @Slf4j @Component public class MyAsyncConfigurer implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { //定义一个最大为10个线程数量的线程池 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的异常,在上述第二步中,我们已经通过重写getAsyncUncaughtExceptionHandler方法,完成了对产生该异常时的处理。
在处理方法中我们简单列出了异常的各个信息。
ok,现在该做的准备工作都已完成,加个测试方法看看结果如何
/** * @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(); } } } }
测试结果如下:
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
测试成功。
到此为止,关于@Async注解的基本使用内容结束。但是在测试过程中,我注意到线程名的命名为默认的pool-1-thread-X,虽然可以分辨出不同的线程在进行作业,但依然很不方便,为什么默认会以这个命名方式进行命名呢?
线程池的命名
点进Executors.newFixedThreadPool,发现在初始化线程池的时候有另一种方法
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
那么这个ThreadFactory为何方神圣?继续ctrl往里点,在类中发现这么一个实现类。
破案了破案了。原来是在这里对线程池进行了命名。那我们只需自己实现ThreadFactory接口,对命名方法进行重写即可,完成代码如下:
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; } }
然后在创建线程池的时候加上新写的类即可对线程名进行自定义。
@Override public Executor getAsyncExecutor() { ExecutorService service = Executors.newFixedThreadPool(10,new MyNamedThreadFactory("HSW")); return service; }
看看重命名后的执行结果。
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实现异步调用:自定义线程池
定义线程池
第一步,先在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创建了一个线程池,同时设置了以下这些参数:
- 核心线程数10:线程池创建时候初始化的线程数
- 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
- 缓冲队列200:用来缓冲执行任务的队列
- 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
- 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
- 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
使用线程池
在定义了线程池之后,我们如何让异步调用的执行任务使用这个线程池中的资源来运行呢?方法非常简单,我们只需要在@Async注解中指定线程池名即可,比如:
@Slf4j @Component public class Task { public static Random random = new Random(); @Async("taskExecutor") public void doTaskOne() throws Exception { log.info("开始做任务一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("完成任务一,耗时:" + (end - start) + "毫秒"); } @Async("taskExecutor") public void doTaskTwo() throws Exception { log.info("开始做任务二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("完成任务二,耗时:" + (end - start) + "毫秒"); } @Async("taskExecutor") public void doTaskThree() throws Exception { log.info("开始做任务三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("完成任务三,耗时:" + (end - start) + "毫秒"); } }
单元测试
最后,我们来写个单元测试来验证一下
@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(); } }
执行上面的单元测试,我们可以在控制台中看到所有输出的线程名前都是之前我们定义的线程池前缀名开始的,说明我们使用线程池来执行异步任务的试验成功了!
2018-03-27 22:01:15.620 INFO 73703 --- [ taskExecutor-1] com.didispace.async.Task : 开始做任务一
2018-03-27 22:01:15.620 INFO 73703 --- [ taskExecutor-2] com.didispace.async.Task : 开始做任务二
2018-03-27 22:01:15.620 INFO 73703 --- [ taskExecutor-3] com.didispace.async.Task : 开始做任务三
2018-03-27 22:01:18.165 INFO 73703 --- [ taskExecutor-2] com.didispace.async.Task : 完成任务二,耗时:2545毫秒
2018-03-27 22:01:22.149 INFO 73703 --- [ taskExecutor-3] com.didispace.async.Task : 完成任务三,耗时:6529毫秒
2018-03-27 22:01:23.912 INFO 73703 --- [ taskExecutor-1] com.didispace.async.Task : 完成任务一,耗时:8292毫秒
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。