同步异步 定时任务 Spring线程池

一、使用线程池的目的:处理异步任务(虽然有同步线程池SyncTaskExecutor,但是本质不算一个线程池,只有同步操作,没有异步调用)

 

二、同步与异步区别

  同步:同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。

  异步:异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。例如, 在某个调用中,需要顺序调用 A, B, C三个过程方法;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方算作过程执行完毕;如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了。在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的业务子线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。

 

三、异步任务使用:@EnableAsync @Async("线程池名")

  1. 注意事项:

    a. 在Spring启动类上添加注解@EnableAsyn或者在你们线程池配置类添加@EnableAsyn(任选其一即可)

    b. @Async不指定value则使用默认异步线程池

    c. 使用此注解的方法的类对象,需要是spring管理下的bean对象(@Component)

  2. Spring默认的异步线程池通过实现AsyncConfigurer接口进行配置

public interface AsyncConfigurer {
 
    Executor getAsyncExecutor();
    
    AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler();
 
}

  Executor : 处理异步方法调用时要使用的实例,

  AsyncUncaughtExceptionHandler :在使用void返回类型的异步方法执行期间抛出异常时要使用的实例。

  3. 实现举例:

@Configuration
public class ThreadPoolConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        threadPool.setCorePoolSize(100);
        // 设置最大线程数
        threadPool.setMaxPoolSize(200);
        // 线程池所使用的缓冲队列
        threadPool.setQueueCapacity(50);
        threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        // 等待任务在关机时完成--表明等待所有线程执行完
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
        threadPool.setAwaitTerminationSeconds(60);
        // 线程名称前缀
        threadPool.setThreadNamePrefix("Derry-Async-");
        // 初始化线程
        threadPool.initialize();

        return threadPool;
    }

    /**
     * 异步异常处理
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}

 4. Spring 已经实现的线程池

  • SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。

  • SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。

  • ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。

  • SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。

  • ThreadPoolTaskExecutor :最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装。

 5. 自定义线程池:

@Component
public class ThreadPool1 {

    //不指定Bean的name则按照方法名创建Bean注入容器
    @Bean(name="myAsync")
    public Executor myAsync() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //最大线程数
        executor.setMaxPoolSize(100);
        //核心线程数
        executor.setCorePoolSize(10);
        //任务队列的大小
        executor.setQueueCapacity(10);
        //线程前缀名
        executor.setThreadNamePrefix("My-Async-ThreadPool-1-");
        //线程存活时间
        executor.setKeepAliveSeconds(30);
        // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
        executor.setAwaitTerminationSeconds(60);
        //等待任务在关机时完成--表明等待所有线程执行完
        executor.setWaitForTasksToCompleteOnShutdown(true);

        /**
         * 拒绝处理策略
         * CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
         * AbortPolicy():直接抛出异常。
         * DiscardPolicy():直接丢弃。
         * DiscardOldestPolicy():丢弃队列中最老的任务。
         */

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        //线程初始化
        executor.initialize();

        return executor;
    }
}

  自定义线程池需要给@Async指定value值

 6. 使用多个线程池进行不同功能的线程隔离

  使用多个线程池,把相同的任务放到同一个线程池中,可以起到隔离的作用,避免有线程出错时影响到其他线程池,例如只有一个线程池时,有两种任务,下单,处理图片,如果线程池被处理图片的任务占满,影响下单任务的进行

 

四、定时任务@EnableScheduling @Scheduled

 说明:

  a. @Scheduled 任务调度注解,主要用于配置定时任务;springboot默认的调度器线程池大小为 1(也就是说在多个方法上加上@schedule的,多个定时任务默认是加入延时队列依次同步执行的)

  b. 在定时任务的方法上加上@Async就会把该定时任务交由异步线程池执行

  配置默认调度器线程池的方法实现SchedulingConfigurer 接口

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(setTaskExecutors());
    }

    @Bean(destroyMethod="shutdown")
    public Executor setTaskExecutors(){
        return Executors.newScheduledThreadPool(3); // 3个线程来处理。
    }
}

 

五、代码演示

 1. 定时任务演示 (为方便演示线程同步执行关系,调度器线程池大小已经配置为3)

//    @Async
    @Scheduled(fixedDelay = 1000*3)  
    public void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"开始执行定时任务");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定时任务执行完毕"+'\n');
    }

  运行结果:

pool-1-thread-1开始执行定时任务
pool-1-thread-1定时任务执行完毕

pool-1-thread-2开始执行定时任务
pool-1-thread-2定时任务执行完毕

该方法的定时任务3s执行一次,但是每次执行要睡眠5s,由于未开启异步所以需要等上一个线程执行完毕,下一个线程才能再次进入该方法。

 2.加上@Async(未指定具体线程池,定时任务交由默认异步线程池执行)

    @Async
    @Transactional
    @Scheduled(fixedDelay = 1000*3)
    public void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"开始执行定时任务");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定时任务执行完毕"+'\n');
    }

  运行结果:

Default-Async-ThreadPool-1开始执行定时任务
Default-Async-ThreadPool-2开始执行定时任务  //线程2在线程1运行期间便执行第二次定时任务,此即异步
Default-Async-ThreadPool-1定时任务执行完毕

Default-Async-ThreadPool-3开始执行定时任务  //线程3在线程2运行期间便执行第三次定时任务
Default-Async-ThreadPool-2定时任务执行完毕

Default-Async-ThreadPool-4开始执行定时任务  
Default-Async-ThreadPool-3定时任务执行完毕

 3. 加上@Async并指定具体线程池

    @Async("myAsync")
    @Transactional
    @Scheduled(fixedDelay = 1000*3)
    public void test2() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"开始执行定时任务");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定时任务执行完毕"+'\n');
    }

  运行结果:

My-Async-ThreadPool-1-1开始执行定时任务  //线程名称发生变化,可见自定义线程池是生效的
My-Async-ThreadPool-1-2开始执行定时任务
My-Async-ThreadPool-1-1定时任务执行完毕

My-Async-ThreadPool-1-3开始执行定时任务
My-Async-ThreadPool-1-2定时任务执行完毕

My-Async-ThreadPool-1-4开始执行定时任务
My-Async-ThreadPool-1-3定时任务执行完毕

 

 4.多线程池隔离异步执行不同定时任务

    @Async //使用默认异步线程池
    @Scheduled(fixedDelay = 1000*3)
    public void test() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"开始执行定时任务");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定时任务执行完毕"+'\n');
    }

    @Async("myAsync") //使用自定义异步线程池
    @Scheduled(fixedDelay = 1000*3)
    public void test2() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"开始执行定时任务");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"定时任务执行完毕"+'\n');
    }

  运行结果:

Default-Async-ThreadPool-1开始执行定时任务
My-Async-ThreadPool-1-1开始执行定时任务
Default-Async-ThreadPool-2开始执行定时任务
My-Async-ThreadPool-1-2开始执行定时任务
Default-Async-ThreadPool-1定时任务执行完毕

My-Async-ThreadPool-1-1定时任务执行完毕

Default-Async-ThreadPool-3开始执行定时任务
My-Async-ThreadPool-1-3开始执行定时任务
Default-Async-ThreadPool-2定时任务执行完毕

Default-Async-ThreadPool-4开始执行定时任务
My-Async-ThreadPool-1-4开始执行定时任务
My-Async-ThreadPool-1-2定时任务执行完毕

 六、相关随笔

  1. Spring最常用的线程池类型ThreadPoolTaskExecutor参数原理详解:https://www.cnblogs.com/JNU-Iot-Longxin/p/15305313.html

  2. ThreadPoolTaskExecutor另一种使用方式(不推荐):https://www.cnblogs.com/JNU-Iot-Longxin/p/15305050.html

posted @ 2022-03-28 15:30  忙碌了一整天的L师傅  阅读(1174)  评论(0编辑  收藏  举报