一篇文章看懂@Scheduled定时器/@Async/CompletableFuture
@Scheduled注解解析:
1.cron:最重要的一个参数
cron表达式[秒] [分] [小时] [日] [月] [周] [年]([年]可省略)
简单了解一下,网上有现成的工具
示例:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
cron表达式使用占位符
另外,cron属性接收的cron表达式支持占位符。eg:
配置文件:
time:cron: */5 * * * * *interval: 5
可通过${}来获取@Scheduled(cron="${time.cron}")void testPlaceholder1() {System.out.println("Execute at " + System.currentTimeMillis());}@Scheduled(cron="*/${time.interval} * * * * *")void testPlaceholder2() {System.out.println("Execute at " + System.currentTimeMillis());}
2.zone
时区,cron表达式会基于该时区解析。通常默认为空字符串,也就是获取服务器所在地的时区。
3、fixDelay和fixDelayString
fixDelay 上一次执行完毕时间点之后多长时间再执行
fixDelayString与fixDelay意义一样,不过是字符串的,并且支持占位符。
4、fixRate和fixRateString
fixRate:上一次开始执行时间点之后多长时间再执行
fixRateString与fixRate意义一样,不过是字符串的,并且支持占位符;
5、initialDelay和initialDelayString
initialDelay:第一次延迟多长时间后再执行;
initialDelayString与initialDelay意义一样,不过是字符串的,并且支持占位符;
6、使用与避坑
配置方法一:项目启动的时候第一次不会执行定时任务,等到第五秒的时候才会执行;
配置方法二:项目已启动就开始执行定时任务
7、定时器默认为单线程,所以如果项目中使用多个定时器要配置线程池 ,通过实现SchedulingConfigurer接口来打到多线程运行定时器的目的
自定义工厂:
8、Spring中@Async用法总结
调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。
在项目应用中,@Async调用线程池,推荐使用自定义线程池的模式。自定义线程池常用方案:重新实现接口AsyncConfigurer。
1. 何为异步调用?
在解释异步调用之前,我们先来看同步调用的定义;同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。
2. 常规的异步调用处理方式
在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况
3. @Async介绍
在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。
4. 基于@Async无返回值调用的异常处理,
配置AsyncUncaughtExceptionHandler,通过AsyncConfigurer自定义线程池,以及异常处理;
@Configuration
@EnableAsync
public class SpringAsyncConfiguration implements AsyncConfigurer {private static final Logger logger = LoggerFactory.getLogger(getClass());@Bean@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(8);executor.setMaxPoolSize(16);executor.setQueueCapacity(64);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.setThreadNamePrefix("SpringAsyncThread-");return executor;}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new SpringAsyncExceptionHandler();}class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {@Overridepublic void handleUncaughtException(Throwable throwable, Method method, Object... obj) {logger.error("Exception occurs in async method", throwable.getMessage());}}}
5.基于@Async返回值的调用的异常处理:
如果异步方法有返回值,那就应当返回AsyncResult类的对象,以便在调用处捕获异常。因为AsyncResult是Future接口的子类,所以也可以通过future.get()获取返回值的时候捕获ExcecutionException。
异步方法:
@Service
public class AsyncService {@Asyncpublic AsyncResult<String> asyncMethodWithResult() {// do something(可能发生异常)return new AsyncResult("hello");}
}
测试调用:
public class Test {private Logger logger = LoggerFactory.getLogger(getClass());@AutowiredAsyncService asyncService;public void test() {try {Future future = asyncService.asyncMethodWithResult();future.get();} catch (ExecutionException e) {logger.error("exception occurs", e);} catch (InterruptedException e) {logger.error("exception occurs", e);}}}
6、@Async调用中的事务处理机制
在@Async标注的方法,同时也加上了@Transactional的时候;在其调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional.
7、Spring 已经实现的线程池
- SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,因为默认每次调用都会创建一个新的线程。
- SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
- ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
- SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
- ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装。
8、 @Async应用默认线程池
Spring应用默认的线程池,指在@Async注解在使用时,不指定线程池的名称。查看源码,@Async的默认线程池为SimpleAsyncTaskExecutor。
9、有返回值CompletableFuture调用
A、JDK5新增了Future接口,用于描述一个异步计算的结果。虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果。
B、CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
C、在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。
10、默认线程池的弊端
在线程池应用中,参考阿里巴巴java开发规范:线程池不允许使用Executors去创建,也不允许使用系统默认的线程池,推荐通过ThreadPoolExecutor的方式,这样的处理方式让开发的工程师更加明确线程池的运行规则,规避资源耗尽的风险。Executors各个方法的弊端:
A、newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
B、newCachedThreadPool和newScheduledThreadPool:要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM
@Async默认异步配置使用的是SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。针对线程创建问题,SimpleAsyncTaskExecutor提供了限流机制,通过concurrencyLimit属性来控制开关,当concurrencyLimit>=0时开启限流机制,默认关闭限流机制即concurrencyLimit=-1,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor并不是严格意义的线程池,达不到线程复用的功能。
11、@Async应用自定义线程池
自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小,同时也易于线程执行异常控制和处理。在设置系统自定义线程池代替默认线程池时,虽可通过多种模式设置,但替换默认线程池最终产生的线程池有且只能设置一个(不能设置多个类实现AsyncConfigurer)。自定义线程池有如下模式:
a、重新实现接口AsyncConfigurer
b、继承AsyncConfigurerSupport
c、配置由自定义的TaskExecutor替代内置的任务执行器
通过查看Spring源码关于@Async的默认调用规则,会优先查询源码中实现AsyncConfigurer这个接口的类,实现这个接口的类为AsyncConfigurerSupport。但默认配置的线程池和异步处理方法均为空,所以,无论是继承或者重新实现接口,都需指定一个线程池。且重新实现 public Executor getAsyncExecutor()方法。
A、 实现接口AsyncConfigurer
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {@Bean("kingAsyncExecutor")public ThreadPoolTaskExecutor executor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();int corePoolSize = 10;executor.setCorePoolSize(corePoolSize);int maxPoolSize = 50;executor.setMaxPoolSize(maxPoolSize);int queueCapacity = 10;executor.setQueueCapacity(queueCapacity);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());String threadNamePrefix = "kingDeeAsyncExecutor-";executor.setThreadNamePrefix(threadNamePrefix);executor.setWaitForTasksToCompleteOnShutdown(true);// 使用自定义的跨线程的请求级别线程工厂类RequestContextThreadFactory threadFactory = RequestContextThreadFactory.getDefault();executor.setThreadFactory(threadFactory);int awaitTerminationSeconds = 5;executor.setAwaitTerminationSeconds(awaitTerminationSeconds);executor.initialize();return executor;}@Overridepublic Executor getAsyncExecutor() {return executor();}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return (ex, method, params) -> ErrorLogger.getInstance().log(String.format("执行异步任务'%s'", method), ex);}
}
B、继承AsyncConfigurerSupport
@Configuration
@EnableAsync
class SpringAsyncConfigurer extends AsyncConfigurerSupport { @Bean public ThreadPoolTaskExecutor asyncExecutor() { ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); threadPool.setCorePoolSize(3); threadPool.setMaxPoolSize(3); threadPool.setWaitForTasksToCompleteOnShutdown(true); threadPool.setAwaitTerminationSeconds(60 * 15); return threadPool; } @Override public Executor getAsyncExecutor() { return asyncExecutor;
} @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return (ex, method, params) -> ErrorLogger.getInstance().log(String.format("执行异步任务'%s'", method), ex);
}
}
12、配置自定义的TaskExecutor
由于AsyncConfigurer的默认线程池在源码中为空,Spring通过beanFactory.getBean(TaskExecutor.class)先查看是否有线程池,未配置时,通过beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class),又查询是否存在默认名称为TaskExecutor的线程池。所以可在项目中,定义名称为TaskExecutor的bean生成一个默认线程池。也可不指定线程池的名称,申明一个线程池,本身底层是基于TaskExecutor.class便可。
@EnableAsync@Configurationpublic class TaskPoolConfig {@Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME)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;} @Bean(name = "new_task")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;}}
##### 13、 创建CompletableFuture对象
CompletableFuture提供了四个静态方法用来创建CompletableFuture对象:
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)Asynsc表示异步,而supplyAsync与runAsync不同在与前者异步返回一个结果,后者是void.第二个函数第二个参数表示是用我们自己创建的线程池,否则采用默认的ForkJoinPool.commonPool()作为它的线程池.其中Supplier是一个函数式接口,代表是一个生成者的意思,传入0个参数,返回一个结果.
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{return "hello world";});
System.out.println(future.get()); //阻塞的获取结果 ''helllo world"
多个线程池
@Async注解,使用系统默认或者自定义的线程池(代替默认线程池)。可在项目中设置多个线程池,在异步调用时,指明需要调用的线程池名称,如@Async(“new_task”)。
参考博客:https://www.cnblogs.com/wlandwl/p/async.html
Spring使用@Async注解