Spring Boot 中@Async、@Scheduled 和线程池的正确使用

ip6*_*696 6 java spring asynchronous scheduled-tasks threadpool

我写了很长时间的问题,很长。但我试图尽可能多地展示我做了什么,什么是不清楚的。请完成阅读并感谢您的耐心等待!

我尝试了很多实验,写了 spring doc spring doc,(在本站写问题)但还是没看懂全貌。

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

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

South 调度程序必须与线程池一起使用并具有不同的设置。例如第一个 - 5 个线程,第二个 - 10 个线程。虽然我明白了,但我尝试了几个选项,最后还是糊涂了,我应该选择什么以及如何更正确地使用它:

为了测试,我创建了 2 个带有逻辑的 bean,并且每次都会从这个 bean 调用方法:

@Slf4j
@Component
public class TestBean {

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

@Slf4j
@Component
public class TestBean2 {

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

我仍然不明白其中的区别,使用什么以及何时使用 -@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
    }
}
Run Code Online (Sandbox Code Playgroud)

输出日志:

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
Run Code Online (Sandbox Code Playgroud)

工作一个线程并每9 秒从第一个 bean 打印日志。之后我补充说TaskScheduler

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

并启动应用程序。输出:

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
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

但得到了类似的输出:

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
Run Code Online (Sandbox Code Playgroud)

之后我读到我需要使用@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");
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

1 秒启动一个新线程。就是这样!但是如果我返回@Scheduled注释怎么办:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();//async method
}
Run Code Online (Sandbox Code Playgroud)

结果与之前的版本相同。正是需要的!

但现在我想使用第二个豆子。我也将方法设为第二个 bean Async 并尝试启动:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();
    testBean2.test();
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

这两种方法都使用 ONEThreadPoolTaskScheduler和 5 个线程。但我需要用不同的ThreadPoolTaskScheduler. 我创建第二个ThreadPoolTaskScheduler

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

并开始:

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
Run Code Online (Sandbox Code Playgroud)

两个 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:没有任何变化。

在完成后,我恢复了代码:

@Scheduled(fixedRate = 1000L)
public void test() {
    testBean.test();
    testBean2.test();
}
Run Code Online (Sandbox Code Playgroud)

@Async与限定符一起使用:

@Async("first")
@Async("second")
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

正是需要的!但我不明白我是否在做正确的事情

如果我改变ThreadPoolTaskSchedulerThreadPoolTaskExecutor一切的工作方式相同。那我应该用什么?

ThreadPoolTaskSchedulerThreadPoolTaskExecutor @ScheduledThreadPoolTaskScheduler/ThreadPoolTaskExecutor来自代码? @Scheduled使用ThreadPoolTaskScheduler/ThreadPoolTaskExecutor来自代码或@Async?

Qua*_*ien 3

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

  1. 您有自定义的ThreadPoolTask​​Schedulers(TaskExecutor Beans),但您没有正确配置@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");
    }
    Run Code Online (Sandbox Code Playgroud)
  2. 后来,您为您的 bean 配置了执行程序名称“first”和“second”,以便您的异步任务正常工作。@Async 查找并找到具有所提供名称的执行器 Bean。

  3. TaskScheduler 是为调度任务而设计的,TaskExecutor 是为异步任务而设计的。ThreadPoolTask​​Scheduler 实现了 TaskScheduler 和 TaskExecutor。ThreadPoolTask​​Executor 实现了 TaskExecutor 而不是 TaskScheduler。

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