Spring 的 异步调用 与 定时任务

异步调用:@Async 与 定时任务:@Scheduled

        为什么把这两个写在一起呢?主要是我觉得他们都涉及到线程池,而且,内容主要是转载的,同时做了一些简单的修改。

目录

1. 异步调用:@Async

1.1. TaskExecutor

1.2. @Async

1.3. 开启异步的配置类

2. 定时任务:@Scheduled

2.1. TaskScheduler

2.2. Trigger

2.3. 定时方法@Scheduled

2.4. 开启定时任务的配置类


1. 异步调用:@Async

        使用多线程通常是创建 Thread 类或实现 Runnable 接口,需要线程池则另外创建 Executors,而 spring 在这方面提供了一些支持。

        注解 @EnableAsync 开启异步调用后,注解 @Async 的方法就属于一个异步执行的方法(或叫多线程任务)。

        默认情况下,Spring将搜索相关的线程池定义:要么在上下文中搜索唯一的TaskExecutor bean,要么搜索名为“taskExecutor”的Executor bean。如果两者都无法解析,则将使用SimpleAsyncTaskExecutor来处理异步方法调用。

        通常,应该定义一个 ThreadPoolTaskExecutor 来使用线程池,使得异步调用的多线程是可控的。

1.1. TaskExecutor

Spring 异步线程池的接口类,其实实质是对 java.util.concurrent.Executor 的封装

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

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

1.2. @Async

        注解 @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;
    }

}

如下方式会使@Async失效

  1. 异步方法使用static修饰
  2. 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
  3. 异步方法不能与异步方法在同一个类中
  4. 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
  5. 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
  6. 在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
  7. 调用被@Async标记的方法的调用者不能和被调用的方法在同一类中不然不会起作用!
  8. 使用@Async时要求是不能有返回值的不然会报错的 因为异步要求是不关心结果的

1.3. 开启异步的配置类

@Configuration
@EnableAsync   // 启动异步调用
public class ThreadPoolTaskConfig {
	
	private static final int corePoolSize = ;       		
	private static final int maxPoolSize = ;			    
	private static final int keepAliveTime = ;			
	private static final int queueCapacity = ;			
	private static final String threadNamePrefix = "Async-Service-"; 
	
	@Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
	public ThreadPoolTaskExecutor getAsyncExecutor(){
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
           executor.setThreadNamePrefix("Async-Service-");  // 线程池名前缀
		executor.setCorePoolSize(10);       // 核心线程数(默认线程数)
		executor.setMaxPoolSize(100);       // 最大线程数
		executor.setQueueCapacity(200);     // 缓冲队列数
		executor.setKeepAliveSeconds(10);   // 允许线程空闲时间(单位:秒)
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());  // 线程池对拒绝任务的处理策略
		// 初始化
		executor.initialize();
		return executor;
	}
} 

2. 定时任务:@Scheduled

2.1. TaskScheduler

TaskScheduler是 spring 的定时任务使用的线程池的关键类

public interface TaskScheduler {
     // 通过Trigger执行任务
     ScheduledFuture schedule(Runnable task, Trigger trigger);
     // 指定时间执行任务
     ScheduledFuture schedule(Runnable task, Date startTime);
     // 指定在指定时间后,循环周期执行任务
     ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
     //  循环周期执行任务
     ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
    // 延迟N时间,在指定日期执行
    ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
    ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

TaskScheduler有两种实现方式:

- TimerManagerTaskScheduler:使用外部对象管理线程池,如 CommonJ TimerManager,适用于多个进程共享线程池

- ThreadPoolTaskScheduler: 如果仅仅在同一进程管理线程池,则推荐使用此对象。它实际使用Java自我的ScheduledExecutorService管理线程池

2.2. Trigger

Trigger是定时任务配置的关键类,配置方法的下次执行时间

public interface Trigger {
    Date nextExecutionTime(TriggerContext triggerContext);
}

方法里的参数TriggerContext是封装了任务最后执行的时间和最后执行完毕的时间

public interface TriggerContext {
    Date lastScheduledExecutionTime();
    Date lastActualExecutionTime();
    Date lastCompletionTime();
}

TriggerContext默认的实现是SimpleTriggerContext,看源码实现非常简单

Trigger的实现类:

- CronTrigger:使用cron表达式定义任务执行时机

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

- PeriodicTrigger:通过“fixed period,”,“initial delay value”,“boolean to indicate whether the period should be interpreted as a fixed-rate or a fixed-delay”3个参数配置任务执行的时机

2.3. 定时方法@Scheduled

通过@Scheduled定义定时任务

注意:定时方法返回值只能是void且不能有传入参数

demo代码及其说明

/**
 * 定时类
 *  不同异步方法:定时方法只能返回void且不能接受任务参数 
 * 
 * @author hry
 *
 */
@Component
public class ScheduleDemo {
    private static final Logger log = Logger.getLogger(ScheduleDemo.class);

    /**
     * 每次方法执行完毕后,等待5s再执行此方法。
     *  同时只能有个线程运行此方法
     */
    @Scheduled(fixedDelay=5000)
    public void fixedDelay() {
        try {
            // 执行方法需要10s
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
        }
        log.info("fixedDelay--");
    }

    /**
     * 每隔5s调用一次此方法,无论之前的方法是否执行完毕
     *  同时可能有N个线程执行此方法
     */
    @Scheduled(fixedRate=5000)
    public void fixedRate() {
        try {
            // 执行方法需要10s
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
        }
        log.info("fixedRate--");
    }

    /***
     * initialDelay: 第一次调用此方法前的等待时间
     */ 
    @Scheduled(initialDelay=1000, fixedRate=5000)
    public void initialDelayAndfixedRate() {
        log.info("initialDelayAndfixedRate--");
    }

    /**
     * 支持cron语法:
     * 每个参数的意义分别是: second, minute, hour, day of month, month, day of week
     *
     * 如下:周一至周五,每隔5s执行一次方法
     */
    @Scheduled(cron="*/5 * * * * SUN-MON")
    public void cron() {
        log.info("cron--");
    }
}

2.4. 开启定时任务的配置类

在启动类或者某配置类上使用注解 @EnableScheduling 就开启了定时任务,例如:

@SpringBootApplication
@EnableScheduling // 启动定时任务
public class ScheduleApplicationWithSchedulingConfigurer {
    private static final Logger log = LoggerFactory.getLogger(ScheduleApplicationWithSchedulingConfigurer.class);

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

默认的定时器是单线程的,想要多线程,则需要自定义线程池

简单配置:

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
    }
}

或者这样配置:

@Configuration
public class MySchedulingConfigurer implements SchedulingConfigurer  {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }

    @Bean(destroyMethod="shutdown")
    public Executor taskExecutor() {
        return  new ScheduledThreadPoolExecutor(10, new ThreadFactory() {
            private AtomicInteger max = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "myScheConfig-" + max.incrementAndGet());
            }
        });
    }
}

 

【侵删】

 

文章参考:

Spring Boot系列二 Spring @Async异步线程池用法总结

Spring Boot系列三 Spring @EnableScheduling 定时任务用法总结