Spring的任务执行器(TaskExecutor)和任务调度器(TaskScheduler)

一、任务执行和调度(Task Execution and Scheduling

1、介绍

        Spring框架使用TaskExecutor和TaskScheduler接口分别为异步执行和任务调度提供抽象。 Spring还提供了那些接口的实现,这些接口在应用服务器环境中支持线程池或委托给CommonJ。 最终,在公共接口背后使用这些实现抽象出了Java SE 5、Java SE 6和Java EE环境之间的差异。

        Spring还提供集成类,用于支持使用计时器(JDK since 1.3的一部分)和Quartz调度器(http://quartz-scheer.org)进行调度。这两个调度器都是使用FactoryBean设置的,分别带有对计时器或触发器实例的可选引用。 此外,Quartz调度器和计时器都有一个方便的类,允许您调用现有目标对象的方法(类似于常规的MethodInvokingFactoryBean操作)。

2、 Spring TaskExecutor抽象

        executor是线程池概念的JDK名称。“executor”命名是由于无法保证底层实现实际上是一个池;执行程序可以是单线程的,甚至是同步的。Spring的抽象隐藏了Java SE和Java EE环境之间的实现细节。

        Spring的TaskExecutor接口等同于java.util.concurrent.Executor接口。实际上,最初它存在的主要原因是在使用线程池时抽象掉了对Java 5的需求。接口有一个方法execute(Runnable task),该方法接受一个基于线程池的语义和配置执行的任务。

        最初创建TaskExecutor是为了在需要时为其他Spring组件提供线程池抽象。诸如ApplicationEventMulticaster、JMS的AbstractMessageListenerContainer和Quartz integration等组件都使用TaskExecutor池线程抽象。但是,如果您的bean需要线程池行为,则可以根据自己的需要使用此抽象。

2.1 TaskExecutor类型

         Spring发行版中包含了许多预先构建的TaskExecutor实现。在任何情况下,您都不应该需要实现自己的。常见的开箱即用的变体是:

  • SyncTaskExecutor   

        此实现不异步执行调用。相反,每次调用都在调用线程中进行。它主要用于不需要多线程的情况下,比如在简单的测试用例中。

  • SimpleAsyncTaskExecutor   

        此实现不重用任何线程,而是为每次调用启动一个新线程。但是,它支持一个并发限制,该限制将阻塞任何超过该限制的调用,直到释放一个槽为止。如果您正在寻找真正的池,请参见下面的ThreadPoolTaskExecutor。

  • ConcurrentTaskExecutor 

        这个实现是java.util.concurrent.Executor的适配器实例。还有一种替代方法,ThreadPoolTaskExecutor,它将Executor配置参数公开为bean属性。很少需要直接使用ConcurrentTaskExecutor,但是如果ThreadPoolTaskExecutor不够灵活,不能满足您的需求,那么ConcurrentTaskExecutor是另一种选择。

  • ThreadPoolTaskExecutor   

        这个实现是最常用的。它公开了用于配置java.util.concurrent的bean属性。ThreadPoolExecutor并将其包装在一个TaskExecutor中。如果您需要适应另一种java.util.concurrent.Executor,建议使用ConcurrentTaskExecutor。

  • WorkManagerTaskExecutor   

        这个实现使用CommonJ WorkManager作为它的支持服务提供者,它是在Spring应用程序上下文中设置基于CommonJ的WebLogic/WebSphere线程池集成的中心便利类。

  • DefaultManagedTaskExecutor     

        此实现在JSR-236兼容的运行时环境(如Java EE 7+应用服务器)中使用jndi获得的ManagedExecutorService,以取代CommonJ WorkManager。

2.2 使用TaskExecutor

        Spring的TaskExecutor实现用作简单的javabean。在下面的示例中,我们定义了一个bean,它使用ThreadPoolTaskExecutor异步打印一组消息。

①新建一个Runnable实现

 
  1. /**

  2. * @author chenzx

  3. * @date 2018-10-22 上午 10:03

  4. */

  5. @Data

  6. @Component

  7. public class MessagePrintExample {

  8.  
  9. @Data

  10. private class MessagePrintTask implements Runnable {

  11. private String message;

  12.  
  13. public MessagePrintTask(String message) {

  14. this.message = message;

  15. }

  16.  
  17. @Override

  18. public void run() {

  19. System.out.println(Thread.currentThread().getName() + ": " + message);

  20. }

  21. }

  22.  
  23. @Autowired

  24. private TaskExecutor taskExecutor;

  25.  
  26. public MessagePrintExample() {

  27. }

  28.  
  29. public void printMessage() {

  30. for (int i = 0; i < 25; i++) {

  31. taskExecutor.execute(new MessagePrintTask("Message" + i));

  32. }

  33. }

  34. }

②配置文件

 
  1. @Configuration

  2. @ComponentScan

  3. @ImportResource(locations = "classpath:spring/applicationContext-task.xml")

  4. public class TaskConfig {

  5. }

引入的spring/applicationContext-task.xml文件

 
  1. <bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">

  2. <property name="corePoolSize" value="5"/>

  3. <property name="maxPoolSize" value="10"/>

  4. <property name="queueCapacity" value="25"/>

  5. </bean>

③测试:

 
  1. @RunWith(SpringJUnit4ClassRunner.class)

  2. @ContextConfiguration(classes = TaskConfig.class)

  3. public class ExecutorTaskTest {

  4.  
  5. @Autowired

  6. MessagePrintExample example;

  7.  
  8. @Test

  9. public void testExecutorTask() {

  10. example.printMessage();

  11. }

  12. }

结果:

        正如您所看到的,与从池中检索线程并执行自己不同,您将Runnable添加到队列中,TaskExecutor使用其内部规则来决定任务何时执行。

        为了配置TaskExecutor将使用的规则,已经公开了简单的bean属性。

3、 Spring TaskScheduler抽象

除了TaskExecutor抽象之外,Spring 3.0还引入了一个任务调度程序,它提供了多种方法来调度将来某个时间点要运行的任务。

 
  1. public interface TaskScheduler {

  2.  
  3. ScheduledFuture schedule(Runnable task, Trigger trigger);

  4.  
  5. ScheduledFuture schedule(Runnable task, Date startTime);

  6.  
  7. ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);

  8.  
  9. ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

  10.  
  11. ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);

  12.  
  13. ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);

  14. }

        最简单的方法是只接受Runnable和Date参数的“schedule”方法,这将导致任务在指定的时间之后运行一次。所有其他方法都能够调度任务以重复运行。固定速率和固定延迟方法用于简单的周期性执行,但是接受触发器的方法要灵活得多。

3.1 Trigger 接口

        触发器接口本质上是受到Spring 3.0 JSR-236的启发,当时,JSR-236还没有正式实现。触发器的基本思想是,执行时间可以根据过去的执行结果甚至任意条件确定。如果这些决定确实考虑了前面执行的结果,则TriggerContext中可以使用这些信息。触发器接口本身非常简单:

 
  1. public interface Trigger {

  2.  
  3.     Date nextExecutionTime(TriggerContext triggerContext);

  4.  
  5. }

        如您所见,TriggerContext是最重要的部分。它封装了所有相关的数据,如果需要,将来可以对其进行扩展。TriggerContext是一个接口(默认情况下使用SimpleTriggerContext实现)。在这里,您可以看到哪些方法可用于触发器实现。

 
  1. public interface TriggerContext {

  2.  
  3.     Date lastScheduledExecutionTime();

  4.  
  5.     Date lastActualExecutionTime();

  6.  
  7.     Date lastCompletionTime();

  8.  
  9. }

3.2 Trigger实现

        Spring提供了触发器接口的两种实现。最有趣的是CronTrigger。它支持基于cron表达式的任务调度。例如,下面的任务计划在每个小时过去15分钟运行,但只在工作日的朝九晚五“工作时间”运行。

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

        另一个现成的实现是接受一个固定周期、一个可选的初始延迟值,以及一个布尔值,指示该周期应该被解释为固定速率还是固定延迟的PeriodiTrigger。由于TaskScheduler接口已经定义了以固定速率或固定延迟调度任务的方法,因此只要可能,应该直接使用这些方法。PeriodicTrigger实现的价值在于,它可以在依赖于触发器抽象的组件中使用。 例如,可以方便地交替使用周期性触发器、基于cron的触发器,甚至自定义触发器实现。这样的组件可以利用依赖注入,以便在外部配置这样的触发器,从而很容易修改或扩展。

3.3 TaskScheduler 实现

        与Spring的TaskExecutor抽象一样,TaskScheduler安排的主要好处是应用程序的调度需求与部署环境解耦。当部署到应用程序服务器环境时,此抽象级别尤其重要,在该环境中,应用程序本身不应该直接创建线程。对于这样的场景, Spring提供了一个TimerManagerTaskScheduler委托给WebLogic/WebSphere上的CommonJ TimerManager,以及一个最近的DefaultManagedTaskScheduler委托给Java EE 7+环境中的JSR-236 ManagedScheduledExecutorService,两者通常都使用JNDI查找配置。

        当不需要外部线程管理时,一个更简单的替代方法是在应用程序中设置一个本地ScheduledExecutorService,可以通过Spring的ConcurrentTaskScheduler进行调整。为了方便起见,Spring还提供了一个ThreadPoolTaskScheduler,它在内部委托给ScheduledExecutorService,提供与ThreadPoolTaskExecutor类似的常见bean样式的配置。这些变量对于在宽松的应用程序服务器环境中(特别是在Tomcat和Jetty上)本地嵌入的线程池设置也非常适用。

示例:

①创建一个Service 

com.segi.spring.task.scheduled.common.MySchedulerService

 
  1. @Service

  2. public class MySchedulerService{

  3.  
  4. private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  5.  
  6. @Autowired

  7. private TaskScheduler scheduler;

  8.  
  9. public void doSchedulerTask() {

  10. scheduler.schedule(() -> System.out.println(Thread.currentThread().getName() + ": " + format.format(new Date())),

  11. new CronTrigger("*/1 * * * * ?"));

  12. }

  13. }

②配置文件

com.segi.spring.task.scheduled.SchedulerConfig

 
  1. @Configuration

  2. @ComponentScan

  3. public class SchedulerConfig{

  4.  
  5. @Bean

  6. public TaskScheduler taskScheduler() {

  7. ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();

  8. taskScheduler.setPoolSize(5);

  9. taskScheduler.initialize();

  10. return taskScheduler;

  11. }

  12. }

③测试

com.segi.spring.task.scheduled.test.ScheduledTest

 
  1. public class ScheduledTest {

  2.  
  3. //还是得在main方法里才有效啊,启动spring容器就有效,在Junit的@Test方法里没有效果。

  4. public static void main(String[] args) {

  5. ApplicationContext context = new AnnotationConfigApplicationContext(SchedulerConfig.class);

  6. MySchedulerService mySchedulerService = context.getBean(MySchedulerService.class);

  7. mySchedulerService.doSchedulerTask();

  8. }

  9. }

结果:

 

4、Scheduling and Asynchronous Execution的注解支持

        Spring为任务调度和异步方法执行提供注释支持。

4.1 EnableScheduling

        要启用对@Scheduled和@Async注释的支持,请将@EnableScheduling和@EnableAsync添加到您的@Configuration类中:

 
  1. @Configuration

  2. @EnableAsync

  3. @EnableScheduling

  4. public class AppConfig {

  5. }

        您可以自由选择应用程序的相关注释。例如,如果您只需要对@Scheduled的支持,只需省略@EnableAsync即可。对于更细粒度的控制,您可以另外实现SchedulingConfigurer和/或AsyncConfigurer接口。有关详细信息,请参见javadoc。

        如果您喜欢XML配置,可以使用<task:annotation-driven>元素。

 
  1. <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>

  2. <task:executor id="myExecutor" pool-size="5"/>

  3. <task:scheduler id="myScheduler" pool-size="10"/>

注意:

1、在上面的XML中,提供了一个executor引用来处理那些与@Async注释的方法对应的任务,并提供了 scheduler 引用来管理那些用@Scheduled注释的方法。

2、 处理@Async注释的默认通知模式是“代理”,它只允许通过代理拦截调用;同一类中的本地调用不能以这种方式被拦截。对于更高级的拦截模式,可以考虑结合编译时或加载时编织切换到“aspectj”模式。

4.2@Scheduled注解

可以将@Scheduled注释与Trigger元数据一起添加到方法中。例如,下面的方法将以固定的延迟每5秒调用一次,这意味着周期将从前一次调用的完成时间开始度量。

 
  1. @Scheduled(fixedDelay=5000)public void doSomething() {

  2. // something that should execute periodically

  3. }

如果需要固定速率的执行,只需更改注释中指定的属性名。在每次调用的连续启动时间之间,每5秒执行以下操作。

 
  1. @Scheduled(fixedRate=5000)public void doSomething() {

  2. // something that should execute periodically

  3. }

对于固定延迟和固定速率任务,可以指定初始延迟,指示在第一次执行方法之前要等待的毫秒数。

 
  1. @Scheduled(initialDelay=1000, fixedRate=5000)public void doSomething() {

  2. // something that should execute periodically

  3. }

如果简单的周期调度没有足够的表达能力,那么可以提供cron表达式。例如,以下命令只在工作日执行。

 
  1. @Scheduled(cron="*/5 * * * * MON-FRI")public void doSomething() {

  2. // something that should execute on weekdays only

  3. }

注:您还可以使用zone属性指定解析cron表达式的时区。

注意,要调度的方法必须有void返回,并且不能期望有任何参数。如果方法需要与应用程序上下文中的其他对象交互,那么这些对象通常是通过依赖注入提供的。

示例:

①创建Service

com.segi.spring.task.scheduled.annotation.SchedulerAnnotationService

 
  1. @Component

  2. public class SchedulerAnnotationService {

  3.  
  4. private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  5.  
  6. @Scheduled(fixedDelay = 5000)

  7. public void doSchedulerTadkWithFixedDelay() {

  8. System.out.println(Thread.currentThread().getName() + ": " + format.format(new Date()));

  9. }

  10. }

②Java配置

com.segi.spring.task.scheduled.SchedulerConfig

 
  1. @Configuration

  2. @ComponentScan

  3. @EnableScheduling

  4. public class SchedulerConfig{

  5.  
  6. @Bean

  7. public TaskScheduler taskScheduler() {

  8. ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();

  9. taskScheduler.setPoolSize(5);

  10. taskScheduler.initialize();

  11. return taskScheduler;

  12. }

  13. }

③测试

 
  1. public class ScheduledTest {

  2.  
  3. public static void main(String[] args) {

  4. ApplicationContext context = new AnnotationConfigApplicationContext(SchedulerConfig.class);

  5. MySchedulerService mySchedulerService = context.getBean(MySchedulerService.class);

  6. mySchedulerService.doSchedulerTask();

  7. }

  8. }

结果:

        从Spring Framework 4.3开始,任何Scope的bean都支持@Scheduled方法。确保您没有在运行时初始化同一个被@Scheduled注释的类的多个实例,除非您确实希望调度对每个此类实例的回调。 与此相关的是,请确保您不会在使用@Scheduled进行注释并在容器中注册为常规Spring bean的bean类上使用@ configurationon: 否则,您将获得两次初始化,一次通过容器,一次通过@ configurationaspect,每次@Scheduled方法都会被调用两次。

4.3  @Async注解

        可以在方法上提供@Async注解,该方法的调用将异步进行。换句话说,调用者将在调用时立即返回,方法的实际执行将发生在提交给Spring TaskExecutor的任务中。在最简单的情况下,注释可以应用于一个返回值为void的方法。

 
  1. @Async

  2. void doSomething() {

  3. // this will be executed asynchronously

  4. }

示例:

①Java配置文件:

com.segi.spring.task.executor.annotation.AnnotationConfig

 
  1. @Configuration

  2. @ComponentScan

  3. @ImportResource(locations = "classpath:spring/applicationContext-task.xml")

  4. public class AnnotationConfig {

  5. }

引入的spring/applicationContext-task.xml文件

 
  1. <bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">

  2. <property name="corePoolSize" value="5"/>

  3. <property name="maxPoolSize" value="10"/>

  4. <property name="queueCapacity" value="25"/>

  5. </bean>

②com.segi.spring.task.executor.annotation.AnnotationAsyncExample

 
  1. @Component

  2. public class AnnotationAsyncExample {

  3.  
  4. @Async

  5. public void voidParamVoidReturn() {

  6. System.out.println("hello, " + Thread.currentThread().getName());

  7. }

  8. }

③测试

 
  1. @RunWith(SpringJUnit4ClassRunner.class)

  2. @ContextConfiguration(classes = AnnotationConfig.class)

  3. public class AnnotationAsyncTest {

  4.  
  5. @Autowired

  6. AnnotationAsyncExampleannotationAsyncExample;

  7.  
  8. @Test

  9. public void testVoidParamVoidReturn() {

  10. for (int i = 0; i < 25; i++) {

  11. annotationAsyncExample.voidParamVoidReturn();

  12. }

  13. }

  14. }

结果:

注:

①如果容器中没有ThreadPoolTaskExecutor实例,调用的是主线程:

Java配置文件:

 
  1. @Configuration

  2. @ComponentScan

  3. //@ImportResource(locations = "classpath:spring/applicationContext-task.xml")

  4. public class AnnotationConfig {

  5. }

结果:

②如果没有用@Async,调用的也是主线程

 
  1. @Component

  2. public class AnnotationAsyncExample {

  3.  
  4. //@Async

  5. public void voidParamVoidReturn() {

  6. System.out.println("hello, " + Thread.currentThread().getName());

  7. }

  8. }

结果:

        与用@Scheduled注释的方法不同,这些方法可以使用参数,因为调用者将在运行时以“正常”方式调用它们,而不是从容器管理的调度任务中调用它们。例如,下面是@Async注释的一个合法应用程序。

 
  1. @Async

  2. void doSomething(String s) {

  3. // this will be executed asynchronously

  4. }

示例:

 
  1. @Component

  2. public class AnnotationAsyncExample {

  3.  
  4. @Async

  5. public void withParamVoidReturn(String msg) {

  6. System.out.println(msg + " " + Thread.currentThread().getName());

  7. }

  8. }

测试:

 
  1. @Component

  2. public class AnnotationAsyncExample {

  3. @Async

  4. public void withParamVoidReturn(String msg) {

  5. System.out.println(msg + " " + Thread.currentThread().getName());

  6. }

  7. }

结果:

 

 

        甚至返回值的方法也可以异步调用。但是,这些方法需要具有Future类型的返回值。这仍然提供了异步执行的好处,以便调用者可以调用Future的get()方法之前执行其他任务。

 
  1. @Async

  2. Future<String> returnSomething(int i) {

  3. // this will be executed asynchronously

  4. }

注: @Async方法不仅可以声明一个普通的java.util.concurrent.Future,也可以是Spring的org.springframework.util.concurrent.ListenableFuture,或者Spring 4.2版本时,JDK 8的java.util.concurrent.CompletableFuture:用于与异步任务进行更丰富的交互,以及与进一步处理步骤进行即时组合。

实例:

 
  1. @Component

  2. public class AnnotationAsyncExample {

  3.  
  4. @Async

  5. public Future<String> withParamWithReturn(String msg) throws Exception {

  6. Callable<String> callable = () -> msg + " " + Thread.currentThread().getName();

  7. FutureTask<String> futureTask = new FutureTask<>(callable);

  8. new Thread(futureTask).start();

  9. return futureTask;

  10. }

  11. }

测试:

 
  1. @RunWith(SpringJUnit4ClassRunner.class)

  2. @ContextConfiguration(classes = AnnotationConfig.class)

  3. public class AnnotationAsyncTest {

  4.  
  5. @Autowired

  6. AnnotationAsyncExample annotationAsyncExample;

  7.  
  8. @Test

  9. public void testWithParamWithReturn() throws Exception {

  10. for (int i = 0; i < 25; i++) {

  11. Future<String> future = annotationAsyncExample.withParamWithReturn("msg" + i);

  12. System.out.println(future.get());

  13. }

  14. }

  15. }

注:如果不是返回Future类型,返回结果为null:

        @Async不能与@PostConstruct这样的生命周期回调一起使用。要异步初始化Spring bean,目前必须使用一个单独的初始化Spring bean,该bean将在目标上调用带@Async注释的方法。

 
  1. public class SampleBeanImpl implements SampleBean {

  2.  
  3. @Async

  4. void doSomething() {

  5. // ...

  6. }

  7.  
  8. }

  9.  
  10. public class SampleBeanInitializer {

  11.  
  12. private final SampleBean bean;

  13.  
  14. public SampleBeanInitializer(SampleBean bean) {

  15. this.bean = bean;

  16. }

  17.  
  18. @PostConstruct

  19. public void initialize() {

  20. bean.doSomething();

  21. }

  22.  
  23. }

注: @Async没有直接对应的XML,因为应该首先为异步执行设计这样的方法,而不是在外部重新声明为async。但是,您可以使用Spring AOP结合自定义切入点手动设置Spring的AsyncExecutionInterceptor。

4.4 @Async执行器选择

        默认情况下,在方法上指定@Async时,将使用的执行器是上面描述的提供给“ annotation-driven”元素的执行器。但是,当需要指示在执行给定方法时应该使用默认值之外的执行器时,可以使用@Async注释的value属性。

 
  1. @Async("otherExecutor")void doSomething(String s) {

  2. // this will be executed asynchronously by "otherExecutor"

  3. }

        在本例中,“otherExecutor”可以是Spring容器中任何执行器 bean的名称,也可以是与任何Executor关联的限定符的名称,例如,由<qualifier>元素或Spring的@Qualifier注释指定。

示例:

 
  1. @Component

  2. public class AnnotationAsyncExample {

  3.  
  4. @Async("executor-two")

  5. public void executeWithMultiExecutor() {

  6. System.out.println("hello, " + Thread.currentThread().getName());

  7. }

  8. }

测试:

 
  1. @RunWith(SpringJUnit4ClassRunner.class)

  2. @ContextConfiguration(classes = AnnotationConfig.class)

  3. public class AnnotationAsyncTest {

  4.  
  5. @Autowired

  6. AnnotationAsyncExample annotationAsyncExample;

  7.  
  8. @Test

  9. public void testExecuteWithMultiExecutor() {

  10. for (int i = 0; i < 25; i++) {

  11. annotationAsyncExample.executeWithMultiExecutor();

  12. }

  13. }

  14. }

结果:

4.5 @Async的异常管理

        当@Async方法具有Future类型的返回值时,很容易管理方法执行期间抛出的异常,因为在对Future结果调用get()方法时将抛出此异常。然而,对于void返回类型,异常是未捕获的,不能传输。对于这些情况,可以提供AsyncUncaughtExceptionHandler来处理此类异常。

 
  1. public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

  2.  
  3. @Override

  4. public void handleUncaughtException(Throwable ex, Method method, Object... params) {

  5. // handle exception

  6. }

  7. }

可以通过AsyncConfigurer或task:annotation-driven XML元素定义自定义AsyncUncaughtExceptionHandler。

示例:

①XML配置方式

AsyncUncaughtExceptionHandler

 
  1. public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

  2. @Override

  3. public void handleUncaughtException(Throwable throwable, Method method, Object... params) {

  4. System.out.println("msg:" + throwable.getMessage());

  5. System.out.println("mehtod:" + method.getName());

  6. System.out.println("params:" + Arrays.asList(params));

  7. }

  8. }

XML配置:

 
  1. <bean id="myAsyncUncaughtExceptionHandler" class="com.segi.spring.task.executor.exception.MyAsyncUncaughtExceptionHandler"/>

  2.  
  3. <task:annotation-driven executor="executor" exception-handler="myAsyncUncaughtExceptionHandler"/>

抛出异常的异步方法:

 
  1. @Async

  2. public void voidParamVoidReturn() {

  3. System.out.println("hello, " + Thread.currentThread().getName());

  4. int i = 1 / 0;

  5. }

测试:

 
  1. @Test

  2. public void testVoidParamVoidReturn() {

  3. for (int i = 0; i < 25; i++) {

  4. annotationAsyncExample.voidParamVoidReturn();

  5. }

  6. }

结果:

②AsyncConfigurer

javaConfig

 
  1. @Configuration

  2. @ComponentScan

  3. @EnableAsync

  4. public class AnnotationConfig implements AsyncConfigurer {

  5.  
  6. @Override

  7. public Executor getAsyncExecutor() {

  8. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

  9. executor.setCorePoolSize(10);

  10. executor.setMaxPoolSize(25);

  11. executor.setQueueCapacity(20);

  12. executor.initialize();

  13. return executor;

  14. }

  15.  
  16. @Override

  17. public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {

  18. return new MyAsyncUncaughtExceptionHandler();

  19. }

  20. }

抛异常的方法

 
  1. @Async

  2. public void voidParamVoidReturn() {

  3. System.out.println("hello, " + Thread.currentThread().getName());

  4. int i = 1 / 0;

  5. }

测试

 
  1. @Test

  2. public void testVoidParamVoidReturn() {

  3. for (int i = 0; i < 25; i++) {

  4. annotationAsyncExample.voidParamVoidReturn();

  5. }

  6. }

结果:

 

5、Task 命名空间

从Spring 3.0开始,有一个用于配置TaskExecutor和TaskScheduler实例的XML命名空间。它还提供了一种便利的方法来配置要用触发器调度的任务。

5.1  'scheduler' 元素

下面的元素将创建具有指定线程池大小的ThreadPoolTaskScheduler实例。

<task:scheduler id="scheduler" pool-size="10"/>

       为'id'属性提供的值将用作池中线程名称的前缀。“scheduler”元素相对简单。如果不提供“池大小”属性,默认线程池将只有一个线程。调度程序没有其他配置选项。

5.2  'executor' 元素

下面将创建一个ThreadPoolTaskExecutor实例:

<task:executor id="executor" pool-size="10"/>

       与上面的调度程序一样,为'id'属性提供的值将用作池中线程名称的前缀(@Async("executorName"),否则使用)。就池大小而言,“executor”元素比“scheduler”元素支持更多的配置选项。首先,ThreadPoolTaskExecutor的线程池本身更具可配置性。执行程序的线程池可能具有不同的核心值和最大大小,而不是单一大小。如果只提供一个值,那么执行器将拥有一个固定大小的线程池(核心大小和最大大小相同)。 但是,“executor”元素的“池大小”属性也接受“min-max”形式的范围。

 
  1. <task:executor

  2. id="executorWithPoolSizeRange"

  3. pool-size="5-25"

  4. queue-capacity="100"/>

使用前缀名称:

 
  1. @Async("executor")

  2. public void voidParamVoidReturn() {

  3. System.out.println("hello, " + Thread.currentThread().getName());

  4. }

测试:

 
  1. @Test

  2. public void testVoidParamVoidReturn() {

  3. for (int i = 0; i < 25; i++) {

  4. annotationAsyncExample.voidParamVoidReturn();

  5. }

  6. }

结果:

不标注名称:

 
  1. @Async

  2. public void voidParamVoidReturn() {

  3. System.out.println("hello, " + Thread.currentThread().getName());

  4. }

结果:

        从配置中可以看到,还提供了一个“ queue-capacity”值。还应该根据执行器的queue-capacity考虑线程池的配置。有  pool size和 queue capacity之间关系的完整描述,请参阅ThreadPoolExecutor的文档。其主要思想是,当提交任务时,如果当前活跃线程的数量小于 core size,执行器将首先尝试使用空闲线程。如果已经达到 core size,那么只要队列的容量未满,任务就会被添加到队列中。 只有在达到queue-capacity时,执行器才会创建一个超出core size的新线程。如果已达到 max size,则执行程序将拒绝该任务。

        默认情况下,队列是无限的,但这不是理想的配置,因为如果在所有池线程繁忙时向队列添加了足够的任务,就会导致outofmemoryerror错误。此外,如果队列是无限的,那么max size根本不起作用。因为执行器将总是在线程数超出core size时,将新建的线程加入队列。一个队列必须是有限的(这就是为什么一个固定大小的池是唯一使用一个无限队列的场景)。

         稍后,我们将回顾keep-alive设置的效果,该设置在提供池大小配置时添加了另一个要考虑的因素。 首先,让我们考虑一下上面提到的拒绝任务的情况。默认情况下,当任务被拒绝时,线程池执行器将抛出TaskRejectedException异常。 然而,拒绝策略实际上是可配置的。在使用默认拒绝策略(即AbortPolicy实现)时引发异常。 对于在重载下可以跳过某些任务的应用程序,可以配置DiscardPolicy或 DiscardOldestPolicy 。 对于需要在重载下限制提交任务的应用程序,另一个很好的选项是CallerRunsPolicy。 该策略将强制调用submit方法的线程运行任务本身,而不是抛出异常或丢弃任务。其思想是这样一个调用者在运行该任务时很忙,不能立即提交其他任务。因此,它提供了一种简单的方法来控制传入负载,同时保持线程池和队列的限制。通常,这允许执行程序“缠住”它正在处理的任务,从而释放队列、池或两者中的一些容量。这些选项中的任何一个都可以从“executor”元素上的“ rejection-policy”属性的枚举值中选择。

 
  1. <task:executor

  2. id="executorWithCallerRunsPolicy"

  3. pool-size="5-25"

  4. queue-capacity="100"

  5. rejection-policy="CALLER_RUNS"/>

         最后,keep-alive设置确定线程在终止之前保持空闲的时间限制(以秒为单位)。如果当前池中的线程数超过核心线程数,那么在等待这段时间而不处理任务之后,多余的线程将被终止。时间值为零将导致执行任务后立即终止多余的线程,而不会在任务队列中保留后续工作。

 
  1. <task:executor

  2. id="executorWithKeepAlive"

  3. pool-size="5-25"

  4. keep-alive="120"/>

5.3  'scheduled-tasks' 元素

        Spring任务名称空间最强大的功能是支持在Spring应用程序上下文中配置要调度的任务。 这遵循了与Spring中其他“方法调用器”类似的方法,例如JMS名称空间提供的用于配置消息驱动pojo的方法。 基本上,“ref”属性可以指向任何spring管理对象,而“method”属性提供要在该对象上调用的方法的名称。下面是一个简单的例子。

 
  1. <task:scheduled-tasks scheduler="myScheduler">

  2. <task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>

  3. </task:scheduled-tasks>

  4.  
  5. <task:scheduler id="myScheduler" pool-size="10"/>

        可以看到,调度程序由外部元素引用,每个单独的任务都包含其触发器元数据的配置。在前面的示例中,该元数据定义了一个具有固定延迟的周期触发器,该延迟指示在每个任务执行完成后等待的毫秒数。另一个选项是“固定利率”,表示无论之前执行多长时间,都应该多久执行一次方法。 此外,对于固定延迟和固定速率任务,可以指定一个“初始延迟”参数,指示方法第一次执行之前等待的毫秒数。为了获得更多的控制,可以提供一个“cron”属性。下面是演示这些其他选项的示例。

 
  1. <task:scheduled-tasks scheduler="myScheduler">

  2. <task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>

  3. <task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>

  4. <task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>

  5. </task:scheduled-tasks>

  6.  
  7. <task:scheduler id="myScheduler" pool-size="10"/>

示例:

pojo

 
  1. @Service

  2. public class XmlConfigSchedulerService {

  3.  
  4. public void doScheduledTask() {

  5. System.out.println(Thread.currentThread().getName() + ":执行");

  6. }

  7. }

配置:

 
  1. @Configuration

  2. @ComponentScan

  3. @ImportResource(locations = "classpath:spring/applicationContext-task.xml")

  4. public class SchedulerConfig/* implements AsyncConfigurer */{

  5.  
  6. }

 
  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3. xmlns:task="http://www.springframework.org/schema/task"

  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  5. xsi:schemaLocation="http://www.springframework.org/schema/beans

  6. http://www.springframework.org/schema/beans/spring-beans.xsd

  7. http://www.springframework.org/schema/task

  8. http://www.springframework.org/schema/task/spring-task.xsd">

  9.  
  10. <task:annotation-driven executor="executor" exception-handler="myAsyncUncaughtExceptionHandler"/>-->

  11. <!--pool-size:没有指定为单线程-->

  12. <task:scheduler id="scheduler" pool-size="10"/>

  13. <task:scheduled-tasks scheduler="scheduler">

  14. <task:scheduled ref="xmlConfigSchedulerService" method="doScheduledTask" fixed-delay="1000"/>

  15. </task:scheduled-tasks>

  16. </beans>

测试:

 
  1. public static void main(String[] args) {

  2. ApplicationContext context = new AnnotationConfigApplicationContext(SchedulerConfig.class);

  3. }

结果:

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值