一篇文章看懂@Scheduled定时器/@Async/CompletableFuture

一篇文章看懂@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
    @Override
    public 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;
    }
 
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
        return new SpringAsyncExceptionHandler();
    }
 
    class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    
    
        @Override
        public 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 {
    
    
 
    @Async
    public AsyncResult<String> asyncMethodWithResult() {
    
    
        // do something(可能发生异常)
 
        return new AsyncResult("hello");
    }
}
测试调用:
public class Test {
    
    
 
    private Logger logger = LoggerFactory.getLogger(getClass());
 
    @Autowired
    AsyncService 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 已经实现的线程池
  1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,因为默认每次调用都会创建一个新的线程。
  2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
  3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
  4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
  5. 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;
    }

    @Override
    public Executor getAsyncExecutor() {
    
    
        return executor();
    }

    @Override
    public 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
 @Configuration
 public 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注解

猜你喜欢

转载自blog.csdn.net/weixin_37766296/article/details/110198007
今日推荐