关于java:Spring-Boot中使用Async的时候千万别忘了线程池的配置

4次阅读

共计 4324 个字符,预计需要花费 11 分钟才能阅读完成。

上一篇咱们介绍了如何应用 @Async 注解来创立异步工作,我能够用这种办法来实现一些并发操作,以减速工作的执行效率。然而,如果只是如前文那样间接简略的创立来应用,可能还是会碰到一些问题。存在有什么问题呢?先来思考下,上面的这个接口,通过异步工作减速执行的实现,是否存在问题或危险呢?

@RestController
public class HelloController {

    @Autowired
    private AsyncTasks asyncTasks;
        
    @GetMapping("/hello")
    public String hello() {
        // 将能够并行的解决逻辑,拆分成三个异步工作同时执行
        CompletableFuture<String> task1 = asyncTasks.doTaskOne();
        CompletableFuture<String> task2 = asyncTasks.doTaskTwo();
        CompletableFuture<String> task3 = asyncTasks.doTaskThree();
        
        CompletableFuture.allOf(task1, task2, task3).join();
        return "Hello World";
    }
}

尽管,从单次接口调用来说,是没有问题的。但当接口被客户端频繁调用的时候,异步工作的数量就会大量增长:3 x n(n 为申请数量),如果工作解决不够快,就很可能会呈现内存溢出的状况。那么为什么会内存溢出呢?根本原因是因为 Spring Boot 默认用于异步工作的线程池是这样配置的:

图中我标出的两个重要参数是须要关注的:

  • queueCapacity:缓冲队列的容量,默认为 INT 的最大值(2 的 31 次方 -1)。
  • maxSize:容许的最大线程数,默认为 INT 的最大值(2 的 31 次方 -1)。

所以,默认状况下,个别工作队列就可能把内存给堆满了。所以,咱们真正应用的时候,还须要对异步工作的执行线程池做一些根底配置,以防止出现内存溢出导致服务不可用的问题。

配置默认线程池

默认线程池的配置很简略,只须要在配置文件中实现即可,次要有以下这些参数:

spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=5
spring.task.execution.pool.queue-capacity=10
spring.task.execution.pool.keep-alive=60s
spring.task.execution.pool.allow-core-thread-timeout=true
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
spring.task.execution.thread-name-prefix=task-

具体配置含意如下:

  • spring.task.execution.pool.core-size:线程池创立时的初始化线程数,默认为 8
  • spring.task.execution.pool.max-size:线程池的最大线程数,默认为 int 最大值
  • spring.task.execution.pool.queue-capacity:用来缓冲执行工作的队列,默认为 int 最大值
  • spring.task.execution.pool.keep-alive:线程终止前容许放弃闲暇的工夫
  • spring.task.execution.pool.allow-core-thread-timeout:是否容许外围线程超时
  • spring.task.execution.shutdown.await-termination:是否期待残余工作实现后才敞开利用
  • spring.task.execution.shutdown.await-termination-period:期待残余工作实现的最大工夫
  • spring.task.execution.thread-name-prefix:线程名的前缀,设置好了之后能够不便咱们在日志中查看解决工作所在的线程池

入手试一试

咱们间接基于之前 chapter7-5 的后果来进行如下操作。

首先,在没有进行线程池配置之前,能够先执行一下单元测试:

@Test
public void test1() throws Exception {long start = System.currentTimeMillis();

    CompletableFuture<String> task1 = asyncTasks.doTaskOne();
    CompletableFuture<String> task2 = asyncTasks.doTaskTwo();
    CompletableFuture<String> task3 = asyncTasks.doTaskThree();

    CompletableFuture.allOf(task1, task2, task3).join();

    long end = System.currentTimeMillis();

    log.info("工作全副实现,总耗时:" + (end - start) + "毫秒");
}

因为默认线程池的外围线程数是 8,所以 3 个工作会同时开始执行,日志输入是这样的:

2021-09-15 00:30:14.819  INFO 77614 --- [task-2] com.didispace.chapter76.AsyncTasks       : 开始做工作二
2021-09-15 00:30:14.819  INFO 77614 --- [task-3] com.didispace.chapter76.AsyncTasks       : 开始做工作三
2021-09-15 00:30:14.819  INFO 77614 --- [task-1] com.didispace.chapter76.AsyncTasks       : 开始做工作一
2021-09-15 00:30:15.491  INFO 77614 --- [task-2] com.didispace.chapter76.AsyncTasks       : 实现工作二,耗时:672 毫秒
2021-09-15 00:30:19.496  INFO 77614 --- [task-3] com.didispace.chapter76.AsyncTasks       : 实现工作三,耗时:4677 毫秒
2021-09-15 00:30:20.443  INFO 77614 --- [task-1] com.didispace.chapter76.AsyncTasks       : 实现工作一,耗时:5624 毫秒
2021-09-15 00:30:20.443  INFO 77614 --- [main] c.d.chapter76.Chapter76ApplicationTests  : 工作全副实现,总耗时:5653 毫秒

接着,能够尝试在配置文件中减少如下的线程池配置

spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=5
spring.task.execution.pool.queue-capacity=10
spring.task.execution.pool.keep-alive=60s
spring.task.execution.pool.allow-core-thread-timeout=true
spring.task.execution.thread-name-prefix=task-

日志输入的程序会变成如下的程序:

2021-09-15 00:31:50.013  INFO 77985 --- [task-1] com.didispace.chapter76.AsyncTasks       : 开始做工作一
2021-09-15 00:31:50.013  INFO 77985 --- [task-2] com.didispace.chapter76.AsyncTasks       : 开始做工作二
2021-09-15 00:31:52.452  INFO 77985 --- [task-1] com.didispace.chapter76.AsyncTasks       : 实现工作一,耗时:2439 毫秒
2021-09-15 00:31:52.452  INFO 77985 --- [task-1] com.didispace.chapter76.AsyncTasks       : 开始做工作三
2021-09-15 00:31:55.880  INFO 77985 --- [task-2] com.didispace.chapter76.AsyncTasks       : 实现工作二,耗时:5867 毫秒
2021-09-15 00:32:00.346  INFO 77985 --- [task-1] com.didispace.chapter76.AsyncTasks       : 实现工作三,耗时:7894 毫秒
2021-09-15 00:32:00.347  INFO 77985 --- [main] c.d.chapter76.Chapter76ApplicationTests  : 工作全副实现,总耗时:10363 毫秒
  • 工作一和工作二会马上占用外围线程,工作三进入队列期待
  • 工作一实现,开释出一个外围线程,工作三从队列中移出,并占用外围线程开始解决

留神:这里可能有的小伙伴会问,最大线程不是 5 么,为什么工作三是进缓冲队列,不是创立新线程来解决吗?这里要了解缓冲队列与最大线程间的关系:只有在缓冲队列满了之后才会申请超过外围线程数的线程来进行解决。所以,这里只有缓冲队列中 10 个工作满了,再来第 11 个工作的时候,才会在线程池中创立第三个线程来解决。这个这里就不具体写列子了,读者能够本人调整下参数,或者调整下单元测试来验证这个逻辑。

本系列教程《Spring Boot 2.x 基础教程》点击中转!,欢送珍藏与转发!如果学习过程中如遇艰难?能够退出咱们 Spring 技术交换群,参加交换与探讨,更好的学习与提高!

代码示例

本文的残缺工程能够查看上面仓库中 2.x 目录下的 chapter7-6 工程:

  • Github:https://github.com/dyc87112/SpringBoot-Learning/
  • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

如果您感觉本文不错,欢送 Star 反对,您的关注是我保持的能源!

欢送关注我的公众号:程序猿 DD,分享其余中央看不到的常识与思考

正文完
 0