浅谈Spring @Async异步线程池用法总结

 更新时间:2017年07月25日 15:10:09   作者:hry2015  
本篇文章主要介绍了浅谈Spring @Async异步线程池用法总结,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文介绍了Spring @Async异步线程池用法总结,分享给大家,希望对大家有帮助

1. TaskExecutor

spring异步线程池的接口类,其实质是Java.util.concurrent.Executor

Spring 已经实现的异常线程池:

1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类
5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装

2. @Async

spring对过@Async定义异步任务

异步的方法有3种

1. 最简单的异步调用,返回值为void
2. 带参数的异步调用 异步方法可以传入参数
3. 异常调用返回Future

详细见代码:

@Component
public class AsyncDemo {
  private static final Logger log = LoggerFactory.getLogger(AsyncDemo.class);

  /**
   * 最简单的异步调用,返回值为void
   */
  @Async
  public void asyncInvokeSimplest() {
    log.info("asyncSimplest");
  }

  /**
   * 带参数的异步调用 异步方法可以传入参数
   * 
   * @param s
   */
  @Async
  public void asyncInvokeWithParameter(String s) {
    log.info("asyncInvokeWithParameter, parementer={}", s);
  }

  /**
   * 异常调用返回Future
   * 
   * @param i
   * @return
   */
  @Async
  public Future<String> asyncInvokeReturnFuture(int i) {
    log.info("asyncInvokeReturnFuture, parementer={}", i);
    Future<String> future;
    try {
      Thread.sleep(1000 * 1);
      future = new AsyncResult<String>("success:" + i);
    } catch (InterruptedException e) {
      future = new AsyncResult<String>("error");
    }
    return future;
  }

}

以上的异步方法和普通的方法调用相同

asyncDemo.asyncInvokeSimplest();
asyncDemo.asyncInvokeWithException("test");
Future<String> future = asyncDemo.asyncInvokeReturnFuture(100);
System.out.println(future.get());

3. Spring 开启异步配置

Spring有两种方法启动配置

1. 注解
2. XML

3.1 通过注解实现

要启动异常方法还需要以下配置

1. @EnableAsync 此注解开户异步调用功能

2. public AsyncTaskExecutor taskExecutor() 方法自定义自己的线程池,线程池前缀”Anno-Executor”。如果不定义,则使用系统默认的线程池。

@SpringBootApplication
@EnableAsync // 启动异步调用
public class AsyncApplicationWithAnnotation {
  private static final Logger log = LoggerFactory.getLogger(AsyncApplicationWithAnnotation.class);

  /**
   * 自定义异步线程池
   * @return
   */
  @Bean
  public AsyncTaskExecutor taskExecutor() { 
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 
    executor.setThreadNamePrefix("Anno-Executor");
    executor.setMaxPoolSize(10); 

    // 设置拒绝策略
    executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
      @Override
      public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // .....
      }
    });
    // 使用预定义的异常处理类
    // executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

    return executor; 
  } 

  public static void main(String[] args) {
    log.info("Start AsyncApplication.. ");
    SpringApplication.run(AsyncApplicationWithAnnotation.class, args);
  }
}

以上的异常方法和普通的方法调用相同

@RunWith(SpringRunner.class)
@SpringBootTest(classes=AsyncApplicationWithAnnotation.class)
public class AsyncApplicationWithAnnotationTests {
  @Autowired
  private AsyncDemo asyncDemo;

  @Test
  public void contextLoads() throws InterruptedException, ExecutionException {
    asyncDemo.asyncInvokeSimplest();
    asyncDemo.asyncInvokeWithParameter("test");
    Future<String> future = asyncDemo.asyncInvokeReturnFuture(100);
    System.out.println(future.get());
  }
}

执行测试用例,输出内容如下:

可以看出主线程的名称为main; 异步方法则使用 Anno-Executor1,可见异常线程池起作用了

2017-03-28 20:00:07.731 INFO 5144 --- [ Anno-Executor1] c.hry.spring.async.annotation.AsyncDemo : asyncSimplest
2017-03-28 20:00:07.732 INFO 5144 --- [ Anno-Executor1] c.hry.spring.async.annotation.AsyncDemo : asyncInvokeWithParameter, parementer=test
2017-03-28 20:00:07.751 INFO 5144 --- [ Anno-Executor1] c.hry.spring.async.annotation.AsyncDemo : asyncInvokeReturnFuture, parementer=100
success:100
2017-03-28 20:00:08.757 INFO 5144 --- [    Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@47af7f3d: startup date [Tue Mar 28 20:00:06 CST 2017]; root of context hierarchy

3.2 通过XML实现

Bean文件配置: spring_async.xml

1. 线程的前缀为xmlExecutor
2. 启动异步线程池配置

  <!-- 等价于 @EnableAsync, executor指定线程池 -->
  <task:annotation-driven executor="xmlExecutor"/>
  <!-- id指定线程池产生线程名称的前缀 -->
  <task:executor
    id="xmlExecutor"
    pool-size="5-25"
    queue-capacity="100"
    keep-alive="120"
    rejection-policy="CALLER_RUNS"/>

线程池参数说明

1. ‘id' : 线程的名称的前缀

2. ‘pool-size':线程池的大小。支持范围”min-max”和固定值(此时线程池core和max sizes相同)

3. ‘queue-capacity' :排队队列长度

○ The main idea is that when a task is submitted, the executor will first try to use a free thread if the number of active threads is currently less than the core size.
○ If the core size has been reached, then the task will be added to the queue as long as its capacity has not yet been reached.
○ Only then, if the queue's capacity has been reached, will the executor create a new thread beyond the core size.
○ If the max size has also been reached, then the executor will reject the task.
○ By default, the queue is unbounded, but this is rarely the desired configuration because it can lead to OutOfMemoryErrors if enough tasks are added to that queue while all pool threads are busy.

4. ‘rejection-policy': 对拒绝的任务处理策略

○ In the default ThreadPoolExecutor.AbortPolicy, the handler throws a runtime RejectedExecutionException upon rejection.
○ In ThreadPoolExecutor.CallerRunsPolicy, the thread that invokes execute itself runs the task. This provides a simple feedback control mechanism that will slow down the rate that new tasks are submitted.
○ In ThreadPoolExecutor.DiscardPolicy, a task that cannot be executed is simply dropped.
○ In ThreadPoolExecutor.DiscardOldestPolicy, if the executor is not shut down, the task at the head of the work queue is dropped, and then execution is retried (which can fail again, causing this to be repeated.)

5. ‘keep-alive' : 线程保活时间(单位秒)

setting determines the time limit (in seconds) for which threads may remain idle before being terminated. If there are more than the core number of threads currently in the pool, after waiting this amount of time without processing a task, excess threads will get terminated. A time value of zero will cause excess threads to terminate immediately after executing a task without remaining follow-up work in the task queue()

异步线程池

@SpringBootApplication
@ImportResource("classpath:/async/spring_async.xml")
public class AsyncApplicationWithXML {
  private static final Logger log = LoggerFactory.getLogger(AsyncApplicationWithXML.class);

  public static void main(String[] args) {
    log.info("Start AsyncApplication.. ");
    SpringApplication.run(AsyncApplicationWithXML.class, args);
  }
}

测试用例

@RunWith(SpringRunner.class)
@SpringBootTest(classes=AsyncApplicationWithXML.class)
public class AsyncApplicationWithXMLTest {
  @Autowired
  private AsyncDemo asyncDemo;

  @Test
  public void contextLoads() throws InterruptedException, ExecutionException {
    asyncDemo.asyncInvokeSimplest();
    asyncDemo.asyncInvokeWithParameter("test");
    Future<String> future = asyncDemo.asyncInvokeReturnFuture(100);
    System.out.println(future.get());
  }
}

运行测试用例,输出内容如下:

可以看出主线程的名称为main; 异步方法则使用 xmlExecutor-x,可见异常线程池起作用了

2017-03-28 20:12:10.540 INFO 12948 --- [      main] c.h.s.a.xml.AsyncApplicationWithXMLTest : Started AsyncApplicationWithXMLTest in 1.441 seconds (JVM running for 2.201)
2017-03-28 20:12:10.718 INFO 12948 --- [ xmlExecutor-2] com.hry.spring.async.xml.AsyncDemo    : asyncInvokeWithParameter, parementer=test
2017-03-28 20:12:10.721 INFO 12948 --- [ xmlExecutor-1] com.hry.spring.async.xml.AsyncDemo    : asyncSimplest
2017-03-28 20:12:10.722 INFO 12948 --- [ xmlExecutor-3] com.hry.spring.async.xml.AsyncDemo    : asyncInvokeReturnFuture, parementer=100
success:100
2017-03-28 20:12:11.729 INFO 12948 --- [    Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@71809907: startup date [Tue Mar 28 20:12:09 CST 2017]; root of context hierarchy

4. 对异步方法的异常处理

在调用方法时,可能出现方法中抛出异常的情况。在异步中主要有有两种异常处理方法:

1. 对于方法返回值是Futrue的异步方法: a) 一种是在调用future的get时捕获异常; b) 在异常方法中直接捕获异常

2. 对于返回值是void的异步方法:通过AsyncUncaughtExceptionHandler处理异常

AsyncExceptionDemo:

@Component
public class AsyncExceptionDemo {
  private static final Logger log = LoggerFactory.getLogger(AsyncExceptionDemo.class);

  /**
   * 最简单的异步调用,返回值为void
   */
  @Async
  public void asyncInvokeSimplest() {
    log.info("asyncSimplest");
  }

  /**
   * 带参数的异步调用 异步方法可以传入参数
   * 对于返回值是void,异常会被AsyncUncaughtExceptionHandler处理掉
   * @param s
   */
  @Async
  public void asyncInvokeWithException(String s) {
    log.info("asyncInvokeWithParameter, parementer={}", s);
    throw new IllegalArgumentException(s);
  }

  /**
   * 异常调用返回Future
   * 对于返回值是Future,不会被AsyncUncaughtExceptionHandler处理,需要我们在方法中捕获异常并处理
   * 或者在调用方在调用Futrue.get时捕获异常进行处理
   * 
   * @param i
   * @return
   */
  @Async
  public Future<String> asyncInvokeReturnFuture(int i) {
    log.info("asyncInvokeReturnFuture, parementer={}", i);
    Future<String> future;
    try {
      Thread.sleep(1000 * 1);
      future = new AsyncResult<String>("success:" + i);
      throw new IllegalArgumentException("a");
    } catch (InterruptedException e) {
      future = new AsyncResult<String>("error");
    } catch(IllegalArgumentException e){
      future = new AsyncResult<String>("error-IllegalArgumentException");
    }
    return future;
  }

}

实现AsyncConfigurer接口对异常线程池更加细粒度的控制

a) 创建线程自己的线程池

b) 对void方法抛出的异常处理的类AsyncUncaughtExceptionHandler

/**
 * 通过实现AsyncConfigurer自定义异常线程池,包含异常处理
 * 
 * @author hry
 *
 */
@Service
public class MyAsyncConfigurer implements AsyncConfigurer{
  private static final Logger log = LoggerFactory.getLogger(MyAsyncConfigurer.class);

  @Override
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); 
    threadPool.setCorePoolSize(1); 
    threadPool.setMaxPoolSize(1); 
    threadPool.setWaitForTasksToCompleteOnShutdown(true); 
    threadPool.setAwaitTerminationSeconds(60 * 15); 
    threadPool.setThreadNamePrefix("MyAsync-");
    threadPool.initialize();
    return threadPool; 
  }

  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
     return new MyAsyncExceptionHandler(); 
  }

  /**
   * 自定义异常处理类
   * @author hry
   *
   */
  class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { 

    @Override 
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) { 
      log.info("Exception message - " + throwable.getMessage()); 
      log.info("Method name - " + method.getName()); 
      for (Object param : obj) { 
        log.info("Parameter value - " + param); 
      } 
    } 

  } 

}

@SpringBootApplication
@EnableAsync // 启动异步调用
public class AsyncApplicationWithAsyncConfigurer {
  private static final Logger log = LoggerFactory.getLogger(AsyncApplicationWithAsyncConfigurer.class);

  public static void main(String[] args) {
    log.info("Start AsyncApplication.. ");
    SpringApplication.run(AsyncApplicationWithAsyncConfigurer.class, args);
  }


}

测试代码

@RunWith(SpringRunner.class)
@SpringBootTest(classes=AsyncApplicationWithAsyncConfigurer.class)
public class AsyncApplicationWithAsyncConfigurerTests {
  @Autowired
  private AsyncExceptionDemo asyncDemo;

  @Test
  public void contextLoads() throws InterruptedException, ExecutionException {
    asyncDemo.asyncInvokeSimplest();
    asyncDemo.asyncInvokeWithException("test");
    Future<String> future = asyncDemo.asyncInvokeReturnFuture(100);
    System.out.println(future.get());
  }
}

运行测试用例

MyAsyncConfigurer 捕获AsyncExceptionDemo 对象在调用asyncInvokeWithException的异常

2017-04-02 16:01:45.591 INFO 11152 --- [   MyAsync-1] c.h.s.a.exception.AsyncExceptionDemo   : asyncSimplest
2017-04-02 16:01:45.605 INFO 11152 --- [   MyAsync-1] c.h.s.a.exception.AsyncExceptionDemo   : asyncInvokeWithParameter, parementer=test
2017-04-02 16:01:45.608 INFO 11152 --- [   MyAsync-1] c.h.s.async.exception.MyAsyncConfigurer : Exception message - test
2017-04-02 16:01:45.608 INFO 11152 --- [   MyAsync-1] c.h.s.async.exception.MyAsyncConfigurer : Method name - asyncInvokeWithException
2017-04-02 16:01:45.608 INFO 11152 --- [   MyAsync-1] c.h.s.async.exception.MyAsyncConfigurer : Parameter value - test
2017-04-02 16:01:45.608 INFO 11152 --- [   MyAsync-1] c.h.s.a.exception.AsyncExceptionDemo   : asyncInvokeReturnFuture, parementer=100
error-IllegalArgumentException
2017-04-02 16:01:46.656 INFO 11152 --- [    Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@47af7f3d: startup date [Sun Apr 02 16:01:44 CST 2017]; root of context hierarchy

5. 源码地址

代码的GITHUB地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • mybatis-plus配置日志两种实现方式

    mybatis-plus配置日志两种实现方式

    这篇文章主要给大家介绍了关于mybatis-plus配置日志两种实现方式的相关资料,Mybatis-plus集成了日志框架,可以将程序运行时产生的日志进行记录,方便开发人员进行问题排查,需要的朋友可以参考下
    2023-09-09
  • 如何把第三方服务注册到spring项目容器中

    如何把第三方服务注册到spring项目容器中

    这篇文章主要为大家介绍了如何把第三方服务注册到spring项目容器中,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • springboot整合mybatis plus与druid详情

    springboot整合mybatis plus与druid详情

    这篇文章主要介绍了springboot整合mybatis plus与druid详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的下伙伴可以参考一下
    2022-09-09
  • SpringBoot使用Thymeleaf自定义标签的实例代码

    SpringBoot使用Thymeleaf自定义标签的实例代码

    这篇文章主要介绍了SpringBoot使用Thymeleaf自定义标签的实例代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Spring中的事件监听器使用学习记录

    Spring中的事件监听器使用学习记录

    Spring框架中的事件监听机制是一种设计模式,它允许你定义和触发事件,同时允许其他组件监听这些事件并在事件发生时作出响应,这篇文章主要介绍了Spring中的事件监听器使用学习,需要的朋友可以参考下
    2024-07-07
  • java匿名内部类实例代码详解

    java匿名内部类实例代码详解

    这篇文章主要介绍了java匿名内部类实例代码详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • java9在interface中定义私有方法详解

    java9在interface中定义私有方法详解

    在本篇内容里小编给大家整理的是一篇关于java9在interface中定义私有方法,有兴趣的朋友们可以学习下。
    2020-10-10
  • Java实现冒泡排序算法

    Java实现冒泡排序算法

    这篇文章主要为大家详细介绍了java实现冒泡排序算法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • 关于maven配置项目一直提示程序包不存在以及scope的坑

    关于maven配置项目一直提示程序包不存在以及scope的坑

    这篇文章主要介绍了关于maven配置项目一直提示程序包不存在以及scope的坑,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • java教程之二个arraylist排序的示例分享

    java教程之二个arraylist排序的示例分享

    常常遇到数组排序的问题,下面提供二个java的arraylist排序示例,需要的朋友可以参考下
    2014-03-03

最新评论