springboot自帶线程池ThreadPoolTaskExecutor使用

这篇具有很好参考价值的文章主要介绍了springboot自帶线程池ThreadPoolTaskExecutor使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

    不管是阿里,还是华为java开发手册,都会有一条建议,就是让开发者不要使用Executors去创建线程池,而是使用构造函数ThreadPoolExecutor的方式来创建,并设置合理的参数。原因如下:

     说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

    在spring框架中,spring提供了ThreadPoolTaskExecutor来创建线程池,该类在spring-context包下。其实ThreadPoolTaskExecutor是对ThreadPoolExecutor的封装。

   到了springboot这里,因为引入了spring-boot-autoconfigurer,自动装配机制,在task包下,直接把ThreadPoolTaskExecutor注入到bean容器里面。所以在springboot项目中,如果要使用线程池,可以直接使用,都不用额外任何配置。

    springboot自动装配的线程池使用的配置如下:

threadpooltaskexecutor,java,ThreadPool,Async,executor,oom

    默认核心线程数是8个。最大线程数和等待队列都是Integer.MAX_VALUE。综合上面的介绍,默认配置的线程池其实也有OOM的风险。 

    这里使用的springboot版本是2.7.8。

   pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springexample</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.8</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.20</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

</project>

    异步任务类:在方法上添加@Async注解,可以让他启用线程池处理异步任务。

/*
 *    xxx co.ltd Copyright @ 2023-2023 All Rights Reserved
 */

package com.example.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.CompletableFuture;

/**
 * 描述信息
 *
 * @author Administrator
 * @since 2023/4/2 7:30
 */
@Slf4j
@Component
public class AsyncTask {
    private Random random = new Random();

    @Async
    public CompletableFuture<String> doTaskOne() throws Exception {
        log.info("task one start.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("task one done,cost " + (end - start) + "ms.");
        return CompletableFuture.completedFuture("task one done");
    }

    @Async
    public CompletableFuture<String> doTaskTwo() throws Exception {
        log.info("task two start.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("task two done,cost " + (end - start) + "ms.");
        return CompletableFuture.completedFuture("task two done");
    }

    @Async
    public CompletableFuture<String> doTaskThree() throws Exception {
        log.info("task three start.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("task three done,cost " + (end - start) + "ms.");
        return CompletableFuture.completedFuture("task three done");
    }
}

    启动类:启动类上添加注解@EnableAsync开启异步

/*
 *    xxx co.ltd Copyright @ 2023-2023 All Rights Reserved
 */

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * 描述信息
 *
 * @author Administrator
 * @since 2023/4/2 7:29
 */
@SpringBootApplication
@EnableAsync
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

    测试类:

/*
 *    xxx co.ltd Copyright @ 2023-2023 All Rights Reserved
 */

package com.example.task;

import com.example.App;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.CompletableFuture;

/**
 * 描述信息
 *
 * @author Administrator
 * @since 2023/4/2 7:34
 */
@SpringBootTest(classes = {App.class})
@Slf4j
public class AsyncTaskTest {

    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void testTask() throws Exception {
        long start = System.currentTimeMillis();
        CompletableFuture<String> taskOne = asyncTask.doTaskOne();
        CompletableFuture<String> taskTwo = asyncTask.doTaskTwo();
        CompletableFuture<String> taskThree = asyncTask.doTaskThree();
        CompletableFuture.allOf(taskOne, taskTwo, taskThree).join();
        long end = System.currentTimeMillis();
        log.info("all task done,cost " + (end - start) + " ms");
    }
}

    这里设置了3个任务,默认线程池核心线程数是8个, 所以这3个任务在线程池环境中,基本都是同时运行,所以总体运行时间肯定会大于他们3各种最耗时的一个任务,小于三个任务耗时之和。

    运行这个测试用例,打印结果:

threadpooltaskexecutor,java,ThreadPool,Async,executor,oom

    从打印结果来看,3个任务几乎同时执行,运行结束,分别耗时:2094ms、 653ms、 1505ms,最后总耗时2129ms,符合预期。 

    这里打印的线程池前缀是task-,也是默认线程池配置。

    在springboot配置中,提供了可以配置线程池的参数:

spring:
  task:
    execution:
      pool:
        core-size: 2
        max-size: 5
        queue-capacity: 10
      thread-name-prefix: test-task-

   这些参数都不是我们自定义的,而是springboot配置文件中指定的参数名。所以我们可以通过yml自动提示类进行配置:

threadpooltaskexecutor,java,ThreadPool,Async,executor,oom 

    这里也可以看出默认线程池配置核心数量是8个, 这里我们设置为2,来验证线程池工作原理。

    这里有3个任务,核心线程数是2,所以只能先执行2个任务,剩下的进入队列等待,当前面一个任务执行完成,最后一个任务才会从等待队列中进入核心线程进行执行,重新运行单元测试,打印信息如下:

threadpooltaskexecutor,java,ThreadPool,Async,executor,oom

    这个打印结果,刚开始任务1,2都运行,任务2完成之后,任务3开始执行。

    因为修改了线程前缀,这里打印的线程前缀是test-task-,从线程前缀 + 线程数上来看,这里最大线程数是2,因为前面设置的核心线程数就是2。

    =========================================

     相信做过springboot线程池相关的测试,可能有的人得出的结论和我这里不太一样,springboot默认线程池是SimpleAsyncTaskExecutor。这个原因呢,有两个,一个是springboot版本的原因,默认不做任何配置,一样的代码,上面运行打印的线程池前缀就是SimpleAsyncTaskExecutor。另外一个原因就是上面提到的ThreadPoolTaskExecutor在配置的时候,其实使用了一个特别的注解:@ConditionalOnMissingBean({Executor.class}),如下所示:

threadpooltaskexecutor,java,ThreadPool,Async,executor,oom

    这个注解的意思是,当bean容器中没有Executor.class这个实例的时候,进行配置。也即是说其他地方配置了线程器Executor,那么这个ThreadPoolTaskExecutor的bean就不会被配置。这也就是大家的结论里面spring线程池默认不是ThreadPoolTaskExecutor的原因。

    我这里通过引入spring-boot-starter-websocket依赖,然后配置websocket:

/*
 *    xxx co.ltd Copyright @ 2023-2023 All Rights Reserved
 */

package com.example.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * 描述信息
 *
 * @author Administrator
 * @since 2023/4/2 20:14
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/tmax/ws").setAllowedOriginPatterns("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/user","/topic");
        registry.setApplicationDestinationPrefixes("/app");
        registry.setUserDestinationPrefix("/user");
    }
}

    同样的,我们再次测试,发现结果如下所示:
threadpooltaskexecutor,java,ThreadPool,Async,executor,oom

    这里也验证了上面@ConditionalOnMissingBean({Executor.class})注解的作用,因为有了别的线程池,所以这里ThreadPoolTaskExecutor线程池就没有被加载。这里的线程池就是SimpleAsyncTaskExecutor。这个线程池其实不是一个真正的线程池,因为它每次都会创建新线程,这个线程池创建的目的其实就是为了执行少量短时间的任务,并不适合在高并发场景下。

    ================================

     通过上面的实验,我们知道,在springboot 2.7.8版本里面,如果没有其他配置,默认线程池就是ThreadPoolTaskExecutor,而且可以不用任何配置就可以使用。但是它还是有OOM的风险,因为它的max-size和queue-capacity都是Integer.MAX_VALUE,所以我们需要修改它的默认线程池配置信息。但是默认线程池有个德性,就是如果配置了其他线程池,它又不会被加载。

     所以一般的项目里面,我们都是进行如下所示的手动配置:

/*
 *    xxx co.ltd Copyright @ 2023-2023 All Rights Reserved
 */

package com.example.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 描述信息
 *
 * @author Administrator
 * @since 2023/4/2 20:36
 */
@Configuration
@EnableAsync
public class ThreadPoolConfig {
    @Value("${spring.task.execution.pool.core-size}")
    private int corePoolSize;
    @Value("${spring.task.execution.pool.max-size}")
    private int maxPoolSize;
    @Value("${spring.task.execution.pool.queue-capacity}")
    private int queueCapacity;
    @Value("${spring.task.execution.thread-name-prefix}")
    private String threadNamePrefix;

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix(threadNamePrefix);
        // 拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

    另外,一定要结合application.yml中设置的线程池配置信息使用,这样才符合文章开头所说的大厂java开发手册中建议使用自定义参数配置线程池,避免OOM风险。 文章来源地址https://www.toymoban.com/news/detail-766969.html

到了这里,关于springboot自帶线程池ThreadPoolTaskExecutor使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot 利用 ThreadPoolTaskExecutor 批量插入数十万条数据

    在批处理插入数据时,如果在单线程环境下是非常耗时的,本篇文章将采用单线程和多线程进行对比,利用 ThreadPoolTaskExecutor 进行多线程批处理插入65w数据,然后和单线程进行对比,最终得到性能优化。 yml 文件配置 spring 容器注入线程池 bean 对象 创建异步线程业务类 创建单

    2024年02月14日
    浏览(41)
  • ThreadPoolTaskExecutor中的execute()方法和submit()方法有什么区别?

    ThreadPoolTaskExecutor是Spring框架中的一个线程池实现,它提供了execute()和submit()两种方法用于启动异步任务。 execute()方法: 返回值类型为void; 接收一个Runnable类型的参数; 直接启动线程执行任务,不会返回一个Future对象; 如果任务抛出异常,线程池中的线程会捕获并记录异常

    2024年02月03日
    浏览(41)
  • Springboot结合线程池的使用

    配置文件 配置类 方式一:线程池结合CompletableFuture来实现 配置线程池类 CompletableFuture使用线程池进行调用 任务类 方式二:使用@EnableAsync和@Async方式实现 在启动类上加@EnableAsync注解 编写线程池配置 使用 任务类 方式三:重写springboot默认的线程池配置 在启动类上加@EnableAsy

    2024年02月01日
    浏览(52)
  • 【SpringBoot】springboot数据使用多线程批量入数据库

    springboot、mybatisPlus、mysql8 mysql8(部署在1核2G的服务器上,很卡,所以下面的数据条数用5000,太大怕不是要等到花儿都谢了 0.0) 共耗时:180121 ms 耗时时间:87217ms 耗时时间: 28235 可见时间从180秒,缩短到了28秒,但是@Transactional对于多线程是控制不了所有的事务的。 Spring实现

    2024年02月02日
    浏览(42)
  • 【二十四】springboot使用EasyExcel和线程池实现多线程导入Excel数据

      springboot篇章整体栏目:  【一】springboot整合swagger(超详细 【二】springboot整合swagger(自定义)(超详细) 【三】springboot整合token(超详细) 【四】springboot整合mybatis-plus(超详细)(上) 【五】springboot整合mybatis-plus(超详细)(下) 【六】springboot整合自定义全局异常

    2023年04月08日
    浏览(65)
  • Springboot中使用线程池的三种方式

    前言 多线程是每个程序员的噩梦,用得好可以提升效率很爽,用得不好就是埋汰的火葬场。 这里不深入介绍,主要是讲解一些标准用法,熟读唐诗三百首,不会作诗也会吟。 这里就介绍一下springboot中的多线程的使用,使用线程连接池去异步执行业务方法。 由于代码中包含详

    2024年02月08日
    浏览(46)
  • java springboot架构 自定义注解保存项目业务日志,使用线程池保存到数据库

    目录 1:pom.xml依赖 2:注解类样例 3:枚举类 4:具体处理方法类 5:线程池类 1:pom.xml依赖 2:注解类样例 3:枚举类 4:具体处理方法类 5:线程池类

    2024年02月15日
    浏览(47)
  • Java21 + SpringBoot3使用Spring Security时如何在子线程中获取到认证信息

    目录 前言 原因分析 解决方案 方案1:手动设置线程中的认证信息 方案2:使用 DelegatingSecurityContextRunnable 创建线程 方案3:修改 Spring Security 安全策略 通过设置JVM参数修改安全策略 通过 SecurityContextHolder 修改安全策略 总结 近日心血来潮想做一个开源项目,目标是做一款可以适

    2024年02月19日
    浏览(37)
  • SpringBoot线程池和Java线程池的实现原理

    @ 目录 使用默认的线程池 方式一:通过 @Async 注解调用 方式二:直接注入 ThreadPoolTaskExecutor 线程池默认配置信息 SpringBoot 线程池的实现原理 覆盖默认的线程池 管理多个线程池 JAVA常用的四种线程池 newCachedThreadPool newFixedThreadPool newScheduledThreadPool newSingleThreadExecutor Java 线程池中

    2023年04月11日
    浏览(45)
  • SpringBoot中启用虚拟线程

    虚拟线程是JDK21版本正式发布的一个新特性。虚拟线程和平台线程主要区别在于,虚拟线程在运行周期内不依赖操作系统线程:它们与硬件脱钩,因此被称为“虚拟”。这种解耦是由JVM提供的抽象层赋予的。 虚拟线程的运行成本远低于平台线程。它们消耗的内存要少得多。这

    2024年02月08日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包