在Spring Boot中正确使用@ Async,@ Scheduled和线程池

时间:2018-09-05 10:56:17

标签: java spring asynchronous scheduled-tasks threadpool

我写了很久很久的问题了。但是我试图尽可能多地展示我所做的事情和不清楚的事情。请完成阅读并感谢您的耐心!

我尝试了许多实验,写了spring doc spring doc,(在此站点中写了问题),但仍然不了解全部内容。

我的任务是在一台spring-boot服务器中实现一些调度程序。

  1. First Scheduler将每1秒检查一次DB中的数据并运行一些逻辑。
  2. 第二调度程序将每10毫秒将请求发送到第三方服务。

South调度程序必须与线程池一起使用,并具有不同的设置。例如,前5个线程,后10个线程。据我所知,我尝试了几种选择,最后感到困惑,我应该选择什么以及如何更正确地使用它:

为了进行测试,我创建了两个具有逻辑的bean,每次都会从该bean中调用方法:

@Slf4j
@Component
public class TestBean {

    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
}

@Slf4j
@Component
public class TestBean2 {

    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("Second bean print");
    }
}

我仍然不了解区别,使用什么以及何时使用-代码中的@Scheduled注释或TaskScheduler。我尝试创建带有@Scheduled批注的方法:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;

    public MyScheduler(TestBean testBean, TestBean2 testBean2) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
    }

    @Scheduled(fixedRate = 1000L)
    public void test() {
        testBean.test();//call method from first bean every 1 sec
    }
}

输出日志:

2018-09-05 13:17:28.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:17:37.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:17:46.799  INFO 10144 --- [pool-1-thread-1] com.example.scheduling.TestBean          : First bean print

工作一个线程,并每隔 9秒从第一个bean打印日志。之后,我添加TaskScheduler

@Bean
ThreadPoolTaskScheduler taskScheduler(){
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(5);
    threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
    threadPoolTaskScheduler.setThreadNamePrefix("TASK_SCHEDULER_FIRST-");
    return threadPoolTaskScheduler;
}

然后启动应用。输出:

2018-09-05 13:21:40.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:21:49.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:21:58.973  INFO 7172 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:22:07.973  INFO 7172 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print

每个 9秒,但是不同的线程从第一个bean打印日志。 之后,我尝试注入TaskScheduler并通过另一种方式运行计划:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;
    private final ThreadPoolTaskScheduler taskScheduler;

    public MyScheduler(TestBean testBean, TestBean2 testBean2, ThreadPoolTaskScheduler taskScheduler) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
        this.taskScheduler = taskScheduler;
    }

    @PostConstruct
    public void test() {
        taskScheduler.scheduleAtFixedRate(testBean::test, 1000L);
        testBean.test();
    }
}

但得到类似的输出:

2018-09-05 13:25:54.541  INFO 7044 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:03.541  INFO 7044 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:12.541  INFO 7044 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:26:21.541  INFO 7044 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print

此后,我读到我需要使用@Async批注并在异步中启动bean的方法:

@Slf4j
@Component
public class TestBean {

    @Async
    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
}

输出:

2018-09-05 13:28:07.868  INFO 8608 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:07.868  INFO 8608 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:08.860  INFO 8608 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:09.860  INFO 8608 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:28:10.860  INFO 8608 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print

1秒开始一个新主题。而已!但是,如果我返回@Scheduled批注怎么办:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();//async method
}

结果与以前的版本相同。到底需要什么!

但是现在我要使用第二个bean。我也将方法放入第二个bean Async中并尝试启动:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();
    testBean2.test();
}

输出:

2018-09-05 13:32:46.079  INFO 11108 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:32:46.079  INFO 11108 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:32:47.074  INFO 11108 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:32:47.074  INFO 11108 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:32:48.074  INFO 11108 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print

两种方法都使用一个带有5个线程的ThreadPoolTaskScheduler。但是我需要以不同的ThreadPoolTaskScheduler开始每个方法。我创建第二个ThreadPoolTaskScheduler

@Bean
ThreadPoolTaskScheduler taskScheduler2(){
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(9);
    threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
    threadPoolTaskScheduler.setThreadNamePrefix("TASK_SCHEDULER_SECOND-");
    return threadPoolTaskScheduler;
}

然后开始:

2018-09-05 13:35:31.152  INFO 14544 --- [           main] c.e.scheduling.SchedulingApplication     : Started SchedulingApplication in 1.669 seconds (JVM running for 2.141)
2018-09-05 13:35:40.134  INFO 14544 --- [cTaskExecutor-2] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:35:40.134  INFO 14544 --- [cTaskExecutor-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:41.127  INFO 14544 --- [cTaskExecutor-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:35:41.127  INFO 14544 --- [cTaskExecutor-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:42.127  INFO 14544 --- [cTaskExecutor-5] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:35:42.127  INFO 14544 --- [cTaskExecutor-6] com.example.scheduling.TestBean2         : Second bean print

两个bean都打印日志,但是带有cTaskExecutor且不使用tasckScheduler1tasckScheduler2

这是我的第一个问题-为什么?如何运作?

现在我尝试使用此实现:

@Slf4j
@Component
public class MyScheduler {

    private final TestBean testBean;
    private final TestBean2 testBean2;
    private final ThreadPoolTaskScheduler poolTaskScheduler1;
    private final ThreadPoolTaskScheduler poolTaskScheduler2;

    public MyScheduler(TestBean testBean, TestBean2 testBean2,
                       @Qualifier("first") ThreadPoolTaskScheduler poolTaskScheduler1,
                       @Qualifier("second") ThreadPoolTaskScheduler poolTaskScheduler2) {
        this.testBean = testBean;
        this.testBean2 = testBean2;
        this.poolTaskScheduler1 = poolTaskScheduler1;
        this.poolTaskScheduler2 = poolTaskScheduler2;
    }

//    @Scheduled(fixedRate = 1000L)
    @PostConstruct
    public void test() {
        poolTaskScheduler1.scheduleAtFixedRate(testBean::test, 1000L);
        poolTaskScheduler2.scheduleAtFixedRate(testBean2::test, 1000L);
    }
}

输出:未更改。

最后,我还原代码:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();
    testBean2.test();
}

并在限定符中使用@Async

@Async("first")
@Async("second")

输出:

2018-09-05 13:44:11.489  INFO 7432 --- [EDULER_SECOND-1] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:11.489  INFO 7432 --- [HEDULER_FIRST-1] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:12.484  INFO 7432 --- [EDULER_SECOND-2] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:12.484  INFO 7432 --- [HEDULER_FIRST-2] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:13.484  INFO 7432 --- [HEDULER_FIRST-3] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:13.484  INFO 7432 --- [EDULER_SECOND-3] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:14.484  INFO 7432 --- [EDULER_SECOND-4] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:14.484  INFO 7432 --- [HEDULER_FIRST-4] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:15.484  INFO 7432 --- [EDULER_SECOND-5] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:15.484  INFO 7432 --- [HEDULER_FIRST-5] com.example.scheduling.TestBean          : First bean print
2018-09-05 13:44:16.483  INFO 7432 --- [EDULER_SECOND-6] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:17.483  INFO 7432 --- [EDULER_SECOND-7] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:18.483  INFO 7432 --- [EDULER_SECOND-8] com.example.scheduling.TestBean2         : Second bean print
2018-09-05 13:44:19.483  INFO 7432 --- [EDULER_SECOND-9] com.example.scheduling.TestBean2         : Second bean print

正是需要的! 但是我不知道我是否在做正确的事

如果我将ThreadPoolTaskScheduler更改为ThreadPoolTaskExecutor,则所有操作均相同。那我该怎么用呢?

ThreadPoolTaskSchedulerThreadPoolTaskExecutor 来自代码的@ScheduledThreadPoolTaskScheduler / ThreadPoolTaskExecutor@ScheduledThreadPoolTaskScheduler / ThreadPoolTaskExecutor来自代码还是@Async

1 个答案:

答案 0 :(得分:1)

让我们一一解答您的问题:

  1. 您具有自定义的ThreadPoolTask​​Schedulers(TaskExecutor Bean),但未正确配置@Async。默认情况下,Spring使用SimpleAsyncTaskExecutor执行您的任务。这就是为什么您只看到带有缩写线程名[cTaskExecutor-1],[cTaskExecutor-2]等的日志的原因。 如果要使用taskScheduler1或taskScheduler2,则必须使用相应的名称配置@Async。

    @Async("taskScheduler1")
    public void test(){
        try {
            Thread.sleep(9000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("First bean print");
    }
  2. 稍后,您为Bean配置了执行器名称“ first”和“ second”,因此您的异步任务有效。 @Async查找并找到具有提供的名称的执行者Bean。

  3. TaskScheduler设计用于计划任务,而TaskExecutor设计用于异步任务。 ThreadPoolTask​​Scheduler实现TaskScheduler和TaskExecutor。 ThreadPoolTask​​Executor实现TaskExecutor,而不是TaskScheduler。

    您正在使用调度任务来触发异步任务,因此ThreadPoolTask​​Scheduler和 ThreadPoolTask​​Executor可以互换,除非您想使用 ThreadPoolTask​​Executor在线程池上的细粒度配置,例如 setCorePoolSize setMaxPoolSize ,..如果您使用多个调度任务,则需要 实现ThreadPoolTask​​Scheduler,因为默认情况下所有@Scheduled任务都是 在Spring创建的大小为1的默认线程池中执行。