1、@EnableAsync首先,我们需要在启动类上添加 @EnableAsync 注解来声明开启异步方法。 @SpringBootApplication @EnableAsync public class SpringbootAsyncApplication { public static void main(String[] args) { SpringApplication.run(SpringbootAsyncApplication.class, args); } }
2、@Async需要注意的,@Async在使用上有一些限制:
原因很简单:
2.1、无返回值的异步方法这是一个异步运行的无返回值方法: @Async public void asyncMethodWithVoidReturnType() { System.out.println("异步无返回值方法 " + Thread.currentThread().getName()); }
实例:
@Component public class AsyncTask { Logger log= LoggerFactory.getLogger(AsyncTask.class); private Random random = new Random(); /** * 定义三个异步式方法 * @throws InterruptedException */ @Async public void taskOne() throws InterruptedException { long start = System.currentTimeMillis(); //随机休眠若干毫秒 Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("任务一执行完成耗时{}秒", (end - start)/1000f); } @Async public void taskTwo() throws InterruptedException { long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("任务二执行完成耗时{}秒", (end - start)/1000f); } @Async public void taskThree() throws InterruptedException { long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("任务三执行完成耗时{}秒", (end - start)/1000f); } }
@SpringBootTest @RunWith(SpringRunner.class) public class AsyncTaskTest { @Autowired private AsyncTask asyncTask; Logger log= LoggerFactory.getLogger(AsyncTaskTest.class); @Test public void doAsyncTasks(){ try { long start = System.currentTimeMillis(); //调用三个异步式方法 asyncTask.taskOne(); asyncTask.taskTwo(); asyncTask.taskThree(); Thread.sleep(5000); long end = System.currentTimeMillis(); log.info("主程序执行完成耗时{}秒", (end - start)/1000f); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:可以看到三个方法没有顺序执行,这个复执行单元测试,您可能会遇到各种不同的结果,比如:
原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。
2.1、有返回值的异步方法@Async也可以应用有返回值的方法–通过在Future中包装实际的返回值: /** * 有返回值的异步方法 * @return */ @Async public Future<String> asyncMethodWithReturnType() { System.out.println("执行有返回值的异步方法 " + Thread.currentThread().getName()); try { Thread.sleep(5000); return new AsyncResult<String>("hello world !!!!"); } catch (InterruptedException e) { // } return null; }
Spring还提供了一个实现Future的AsyncResult类。这个类可用于跟踪异步方法执行的结果。
实例:
@Async public Future<String> taskOne() throws InterruptedException { long start = System.currentTimeMillis(); //随机休眠若干毫秒 Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("任务一执行完成耗时{}秒", (end - start)/1000f); return new AsyncResult<>("任务一完事了"); }
taskTwo、taskThree方法做同样的改造。
@Test public void doFutureTask(){ try { long start=System.currentTimeMillis(); Future<String> future1=asyncTask.taskOne(); Future <String> future2 = asyncTask.taskTwo(); Future <String> future3 = asyncTask.taskThree(); //三个任务执行完再执行主程序 do { Thread.sleep(100); } while (future1.isDone() && future2.isDone() && future3.isDone()); log.info("获取异步方法的返回值:{}", future1.get()); Thread.sleep(5000); long end = System.currentTimeMillis(); log.info("主程序执行完成耗时{}秒", (end - start)/1000f); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
运行结果:可以看到三个任务完成后才执行主程序,还输出了异步方法的返回值。
3、 Executor默认情况下,Spring使用SimpleAsyncTaskExecutor异步运行这些方法。 可以在两个级别上重写默认线程池——应用程序级别或方法级别。
3.1、方法级别重写Executor所需的执行程序需要在配置类中声明 Executor: @Configuration @EnableAsync public class SpringAsyncConfig { @Bean(name = "threadPoolTaskExecutor") public Executor threadPoolTaskExecutor() { return new ThreadPoolTaskExecutor(); } }
然后,在@Async中的属性提供Executor名称: @Async("threadPoolTaskExecutor") public void asyncMethodWithConfiguredExecutor() { System.out.println("Execute method with configured executor - " + Thread.currentThread().getName()); }
3.2、应用级别重写Executor配置类应实现AsyncConfigurer接口,重写getAsyncExecutor()方法。 在这里,我们将返回整个应用程序的Executor,这样一来,它就成为运行以@Async注释的方法的默认Executor: @Configuration @EnableAsync public class SpringApplicationAsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { return new ThreadPoolTaskExecutor(); } }
3.3、自定义线程池配置在上面,自定义线程池只是简单地返回了一个线程池: return new ThreadPoolTaskExecutor();
实际上,还可以对线程池做一些配置: @Configuration @EnableAsync public class SpringPropertiesAsyncConfig implements AsyncConfigurer { /** * 对线程池进行配置 * @return */ @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(20); taskExecutor.setMaxPoolSize(200); taskExecutor.setQueueCapacity(25); taskExecutor.setKeepAliveSeconds(200); taskExecutor.setThreadNamePrefix("oKong-"); // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } }
ThreadPoolTaskExecutor配置参数的简单说明:
4、异常处理当方法返回类型为Future时,异常处理很容易– Future.get()方法将抛出异常。 但是如果是无返回值的异步方法,异常不会传播到调用线程。因此,我们需要添加额外的配置来处理异常。 我们将通过实现AsyncUncaughtExceptionHandler接口来创建自定义异步异常处理程序。 当存在任何未捕获的异步异常时,将调用handleUncaughtException()方法: public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { System.out.println("Exception message - " + throwable.getMessage()); System.out.println("Method name - " + method.getName()); for (Object param : objects) { System.out.println("Parameter value - " + param); } } }
上面,我们使用配置类实现了AsyncConfigurer接口。 作为其中的一部分,我们还需要重写getAsyncUncaughtExceptionHandler()方法以返回我们的自定义异步异常处理: /** * 返回自定义异常处理 * @return */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); }
5、总结这里异步请求的使用及相关配置,如超时,异常等处理。在剥离一些和业务无关的操作时,就可以考虑使用异步调用进行其他无关业务操作,以此提供业务的处理效率。或者一些业务场景下可拆分出多个方法进行同步执行又互不影响时,也可以考虑使用异步调用方式提供执行效率。 1、@EnableAsync首先,我们需要在启动类上添加 @EnableAsync 注解来声明开启异步方法。 @SpringBootApplication @EnableAsync public class SpringbootAsyncApplication { public static void main(String[] args) { SpringApplication.run(SpringbootAsyncApplication.class, args); } }
2、@Async需要注意的,@Async在使用上有一些限制:
原因很简单:
2.1、无返回值的异步方法这是一个异步运行的无返回值方法: @Async public void asyncMethodWithVoidReturnType() { System.out.println("异步无返回值方法 " + Thread.currentThread().getName()); }
实例:
@Component public class AsyncTask { Logger log= LoggerFactory.getLogger(AsyncTask.class); private Random random = new Random(); /** * 定义三个异步式方法 * @throws InterruptedException */ @Async public void taskOne() throws InterruptedException { long start = System.currentTimeMillis(); //随机休眠若干毫秒 Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("任务一执行完成耗时{}秒", (end - start)/1000f); } @Async public void taskTwo() throws InterruptedException { long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("任务二执行完成耗时{}秒", (end - start)/1000f); } @Async public void taskThree() throws InterruptedException { long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("任务三执行完成耗时{}秒", (end - start)/1000f); } }
@SpringBootTest @RunWith(SpringRunner.class) public class AsyncTaskTest { @Autowired private AsyncTask asyncTask; Logger log= LoggerFactory.getLogger(AsyncTaskTest.class); @Test public void doAsyncTasks(){ try { long start = System.currentTimeMillis(); //调用三个异步式方法 asyncTask.taskOne(); asyncTask.taskTwo(); asyncTask.taskThree(); Thread.sleep(5000); long end = System.currentTimeMillis(); log.info("主程序执行完成耗时{}秒", (end - start)/1000f); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:可以看到三个方法没有顺序执行,这个复执行单元测试,您可能会遇到各种不同的结果,比如:
原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。
2.1、有返回值的异步方法@Async也可以应用有返回值的方法–通过在Future中包装实际的返回值: /** * 有返回值的异步方法 * @return */ @Async public Future<String> asyncMethodWithReturnType() { System.out.println("执行有返回值的异步方法 " + Thread.currentThread().getName()); try { Thread.sleep(5000); return new AsyncResult<String>("hello world !!!!"); } catch (InterruptedException e) { // } return null; } Spring还提供了一个实现Future的AsyncResult类。这个类可用于跟踪异步方法执行的结果。
实例:
@Async public Future<String> taskOne() throws InterruptedException { long start = System.currentTimeMillis(); //随机休眠若干毫秒 Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("任务一执行完成耗时{}秒", (end - start)/1000f); return new AsyncResult<>("任务一完事了"); }
taskTwo、taskThree方法做同样的改造。
@Test public void doFutureTask(){ try { long start=System.currentTimeMillis(); Future<String> future1=asyncTask.taskOne(); Future <String> future2 = asyncTask.taskTwo(); Future <String> future3 = asyncTask.taskThree(); //三个任务执行完再执行主程序 do { Thread.sleep(100); } while (future1.isDone() && future2.isDone() && future3.isDone()); log.info("获取异步方法的返回值:{}", future1.get()); Thread.sleep(5000); long end = System.currentTimeMillis(); log.info("主程序执行完成耗时{}秒", (end - start)/1000f); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Test public void doFutureTask(){ try { long start=System.currentTimeMillis(); Future<String> future1=asyncTask.taskOne(); Future <String> future2 = asyncTask.taskTwo(); Future <String> future3 = asyncTask.taskThree(); //三个任务执行完再执行主程序 do { Thread.sleep(100); } while (future1.isDone() && future2.isDone() && future3.isDone()); log.info("获取异步方法的返回值:{}", future1.get()); Thread.sleep(5000); long end = System.currentTimeMillis(); log.info("主程序执行完成耗时{}秒", (end - start)/1000f); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
运行结果:可以看到三个任务完成后才执行主程序,还输出了异步方法的返回值。
3、 Executor默认情况下,Spring使用SimpleAsyncTaskExecutor异步运行这些方法。 可以在两个级别上重写默认线程池——应用程序级别或方法级别。
3.1、方法级别重写Executor所需的执行程序需要在配置类中声明 Executor: @Configuration @EnableAsync public class SpringAsyncConfig { @Bean(name = "threadPoolTaskExecutor") public Executor threadPoolTaskExecutor() { return new ThreadPoolTaskExecutor(); } }
然后,在@Async中的属性提供Executor名称: @Async("threadPoolTaskExecutor") public void asyncMethodWithConfiguredExecutor() { System.out.println("Execute method with configured executor - " + Thread.currentThread().getName()); }
3.2、应用级别重写Executor配置类应实现AsyncConfigurer接口,重写getAsyncExecutor()方法。 在这里,我们将返回整个应用程序的Executor,这样一来,它就成为运行以@Async注释的方法的默认Executor: @Configuration @EnableAsync public class SpringApplicationAsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { return new ThreadPoolTaskExecutor(); } }
3.3、自定义线程池配置在上面,自定义线程池只是简单地返回了一个线程池: return new ThreadPoolTaskExecutor();
实际上,还可以对线程池做一些配置: @Configuration @EnableAsync public class SpringPropertiesAsyncConfig implements AsyncConfigurer { /** * 对线程池进行配置 * @return */ @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(20); taskExecutor.setMaxPoolSize(200); taskExecutor.setQueueCapacity(25); taskExecutor.setKeepAliveSeconds(200); taskExecutor.setThreadNamePrefix("oKong-"); // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } }
ThreadPoolTaskExecutor配置参数的简单说明:
4、异常处理当方法返回类型为Future时,异常处理很容易– Future.get()方法将抛出异常。 但是如果是无返回值的异步方法,异常不会传播到调用线程。因此,我们需要添加额外的配置来处理异常。 我们将通过实现AsyncUncaughtExceptionHandler接口来创建自定义异步异常处理程序。 当存在任何未捕获的异步异常时,将调用handleUncaughtException()方法: public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { System.out.println("Exception message - " + throwable.getMessage()); System.out.println("Method name - " + method.getName()); for (Object param : objects) { System.out.println("Parameter value - " + param); } } }
上面,我们使用配置类实现了AsyncConfigurer接口。 作为其中的一部分,我们还需要重写getAsyncUncaughtExceptionHandler()方法以返回我们的自定义异步异常处理: /** * 返回自定义异常处理 * @return */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); }
5、总结这里异步请求的使用及相关配置,如超时,异常等处理。在剥离一些和业务无关的操作时,就可以考虑使用异步调用进行其他无关业务操作,以此提供业务的处理效率。或者一些业务场景下可拆分出多个方法进行同步执行又互不影响时,也可以考虑使用异步调用方式提供执行效率。 |
|