第一次使用ThreadPoolTaskExecutor实现线程池的经历,反复修改了多次代码才正常使用

这篇具有很好参考价值的文章主要介绍了第一次使用ThreadPoolTaskExecutor实现线程池的经历,反复修改了多次代码才正常使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、前言

  在一个向第三方平台推送消息的场景中,为了提高程序的执行效率,每次发送消息,都创建一个新的线程来完成发送消息的任务,为了提供线程的使用性能,我选择了ThreadPoolTaskExecutor线程池,结果在使用的过程中,出现了较多的问题,这里记录一下避免以后再出现类似的错误(这些错误是不应该出现的,还是对ThreadPoolTaskExecutor使用不熟悉造成的)。

2、ThreadPoolTaskExecutor用法简介

  ThreadPoolTaskExecutor是Spring Framework提供的一个线程池实现,它继承自ThreadPoolExecutor类,并实现了AsyncTaskExecutor和SchedulingTaskExecutor接口,可以用于异步任务执行和定时任务调度。
  我们通过配置类定义了一个ThreadPoolTaskExecutor的Bean,并设置了核心线程数、最大线程数、队列容量和线程名称前缀等参数。然后在MyService类中注入了这个线程池,并在executeTask方法中使用taskExecutor.execute()来提交一个异步任务。
  通过这种方式,我们可以方便地使用ThreadPoolTaskExecutor来执行异步任务,实现多线程处理和任务调度的功能。

  以下是ThreadPoolTaskExecutor的基本用法示例:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class ExecutorConfig {
    
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(10);
        executor.setThreadNamePrefix("MyThread-");
        executor.initialize();
        return executor;
    }
}

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

@Component
public class MyService {

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    public void executeTask() {
        taskExecutor.execute(() -> {
            // 执行异步任务逻辑
            // ...
        });
    }
}

3、ThreadPoolTaskExecutor使用过程复现

3.1、最开始的代码——线程泄露

  以下代码是最开始的一版代码,这里出现了一个非常严重的错误,直接造成了线程泄露,或者说是完全错用了线程池,反而造成了更多的线程资源浪费。

/**
 * 消息推送
 * 20230609 hsh
 */
public class PushNotificationService {

    private static Logger logger = LoggerFactory.getLogger(PushNotificationService.class);
    /**
     * 线程池
     * @return
     */
    public static Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(1000);
        executor.setThreadNamePrefix("AsyncThread-");
        executor.initialize();
        return executor;
    }
    /**
     * 消息推送
     */
    public static void pushNotification(EventTask eventTask, Map<String,Object> param) {
       Executor executor = getAsyncExecutor();
       executor.execute(new Runnable() {
           @Override
           public void run() {
                if(this.isSend(param)){//判断是否需要推送
                   String msg = "";
                   sendMsg(eventInfo);
                }
           }

           private boolean isSend(Map<String, Object> param) {
                
               return true;
           }
           private String sendMsg(String msg){
               
               return "发送";
           }
       });
    }

}

  上述代码的问题,主要发生在获取线程池的问题上,即每次发送消息都调用了getAsyncExecutor()方法,本意是获取一个线程,结果这里每次都会创建一个线程池,而且线程数至少会有10个,所以每次本来只需要创建一个线程处理消息发送,结果每次都创建了一个线程池,每个线程池至少还有10个线程,结果就出现了线程泄露问题。

3.2、第一次修改——解决线程泄露问题,但是线程不安全,在多线程环境下还是可能会导致创建多个线程池实例

  为了解决最开始出现的线程泄露问题,我把ThreadPoolTaskExecutor 作为对象的一个变量,每次获取的时候,判断是否为空,如果不为空时,就不再创建了。

/**
 * 消息推送
 * 20230609 hsh
 */
public class PushNotificationService {

    private static Logger logger = LoggerFactory.getLogger(PushNotificationService.class);
    
	private static ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    /**
     * 线程池
     * @return
     */
    public static Executor getAsyncExecutor() {
    	if(executor == null){
            executor = new ThreadPoolTaskExecutor();
        }
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(1000);
        executor.setThreadNamePrefix("AsyncThread-");
        executor.initialize();
        return executor;
    }
    //省略……

}

  如果多个线程同时调用getAsyncExecutor()方法,还是可能会导致创建多个线程池实例。

3.3、第二次修改——解决线程安全问题,带来了性能问题

  为了解决线程安全问题,我直接使用了synchronized 关键字,代码如下:

/**
 * 消息推送
 * 20230609 hsh
 */
public class PushNotificationService {

    private static Logger logger = LoggerFactory.getLogger(PushNotificationService.class);
    
	private static ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    /**
     * 线程池
     * @return
     */
    public static Executor getAsyncExecutor() {
    	 synchronized (executor){
            if(executor == null){
                executor = new ThreadPoolTaskExecutor();
            }
            executor.setCorePoolSize(30);
            executor.setMaxPoolSize(100);
            executor.setQueueCapacity(1000);
            executor.setThreadNamePrefix("AsyncThread-");
            executor.initialize();
            return executor;
        }
    }
    //省略……
}

  为了解决线程安全问题,我直接使用了synchronized 关键字,上述代码虽然可以正常运行,但是带来了非常严重的性能问题,因为synchronized 关键字包含了整个代码块,就相当于在getAsyncExecutor() 方法上使用了synchronized 关键字,该方法就变成了单线程执行了,所以效率非常低。

3.4、第二次修改——解决性能问题,带来了初始化异常

  为了解决性能问题,使用双重检查锁定(double-checked locking)机制来确保线程安全同时保证处理性能,代码如下:

/**
 * 消息推送
 * 20230609 hsh
 */
public class PushNotificationService {

    private static Logger logger = LoggerFactory.getLogger(PushNotificationService.class);
    
	private static ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    /**
     * 线程池
     * @return
     */
    public static Executor getAsyncExecutor() {
    	 if (executor == null) {
            synchronized (executor) {
                if (executor == null) {
                    executor = new ThreadPoolTaskExecutor();
                    executor.setCorePoolSize(30);
                    executor.setMaxPoolSize(100);
                    executor.setQueueCapacity(1000);
                    executor.setThreadNamePrefix("AsyncThread-");
                    executor.initialize();
                }
            }
        }
        return executor;
    }
    //省略……
}

  通过双重检查锁定,确实可以实现性能的提升,但是这里忽略了一个细节,就是ThreadPoolTaskExecutor 初始化,因为这里当executor 对象不为空时,直接返回了,没有进行initialize()操作,所以报了“ThreadPoolTaskExecutor not initialized ”错误,因此声明ThreadPoolTaskExecutor executor 对象的时候,不能使用new ThreadPoolTaskExecutor()方法进行定义,同时为了避免指令重排序可能带来的问题,需要将 executor 声明为 volatile 类型,以确保在多线程环境下的可见性和正确的初始化顺序。文章来源地址https://www.toymoban.com/news/detail-481550.html

/**
 * 消息推送
 * 20230609 hsh
 */
public class PushNotificationService {

    private static Logger logger = LoggerFactory.getLogger(PushNotificationService.class);
    
	private static volatile ThreadPoolTaskExecutor executor;
    /**
     * 线程池
     * @return
     */
    public static Executor getAsyncExecutor() {
    	 if (executor == null) {
            synchronized (executor) {
                if (executor == null) {
                    executor = new ThreadPoolTaskExecutor();
                    executor.setCorePoolSize(30);
                    executor.setMaxPoolSize(100);
                    executor.setQueueCapacity(1000);
                    executor.setThreadNamePrefix("AsyncThread-");
                    executor.initialize();
                }
            }
        }
        return executor;
    }
    //省略……
}

4、总结

  1. 知识点1:ThreadPoolTaskExecutor用法
  2. 知识点2:synchronized 关键字
  3. 知识点3:双重检查锁定(double-checked locking)机制
  4. 知识点4:volatile 关键字

到了这里,关于第一次使用ThreadPoolTaskExecutor实现线程池的经历,反复修改了多次代码才正常使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • MidJourney使用教程:一 第一次怎么用Midjourney

    实际我是先写的prompts提示这部分,觉得Midjurney使用的方式,市面上已经有一大把文章了,另一方面觉得也没什么可写的。注册一个discard账号写个prompts描述出图就可以了,但其实有很多点其实忽略掉。比如图出来了,这四幅图底下的U1到U4,V1到V4什么意思?还有就是公共频道

    2024年02月11日
    浏览(47)
  • 第一次使用HbuilderX运行微信小程序项目

     点击设置  再点击运行配置,找到小程序运行配置,设置自己对应的路径 如果没有配置路径,则会报错:  然后粘贴自己的appid就好了

    2024年02月13日
    浏览(56)
  • 华为卡托E3276s-150第一次使用怎么配置?

    最近买了有一个电信的4G网卡,型号是E3276s-150,由于很多朋友第一次使用的时候不知道该怎么配置,导致无法正常使用4G上网,现将我的配置方法分享给大家。 1、故障现象    第一次使用电信4G上网卡不知道怎么配置,无法正常使用4G上网 2、故障判断    网卡本身无问题,客

    2024年02月08日
    浏览(46)
  • Spring Boot实现第一次启动时自动初始化数据库

    在现在的后端开发中,只要是运用联系型数据库,信任SSM架构(Spring Boot + MyBatis)已经成为首选。 不过在咱们第一次运转或许布置项目的时分,一般要先手动衔接数据库,履行一个SQL文件以创立数据库以及数据库表格完结 数据库的初始化作业 ,这样咱们的SSM应用程序才能够

    2024年02月03日
    浏览(50)
  • 第一次使用git将远程仓库的代码拉取到本地

    首先默认你已经安装好了git的客户端,如果没安装请先确保已经安装了git的客户端再进行后续的操作 第一步:进入你要克隆的文件夹下,然后点击Git Bash Here 第二步:找到远程仓库的地址,并复制该地址(这里以github上的举例) 第三步:使用下面的git命令从远程仓库复制代码到本

    2024年02月11日
    浏览(77)
  • 在Windows第一次使用使用vcpkg来安装三方库,例如nanomsg、nng、libpqxx

    够早了吧 需要从github上找三方库来使用,看了许多教程后决定为后来者写点简单的流程。记得先装git,安装git教程如下: 到官网下载git添加链接描述 点击下载然后安装。 安装完后会有一系列的应用可以使用 我们主要用的是Git Bash。 vcpkg是微软的包管理工具,可以直接下载三

    2023年04月20日
    浏览(49)
  • 使用git工具上传代码,超细讲解,针对第一次玩git的小伙伴

    第一步安装git管理工具 首先我们要去git官网下载git 安装过的小伙伴可以跳过这步 创建一个文件 git clone 远程代码地址 用来克隆别人写的项目 或者找一个你想上传代码的文件夹,点进文件里面,右击打开,找的Git Bash打开 origin 远程仓库名,可以换成别的名称 master 远程仓库主

    2024年02月07日
    浏览(79)
  • 当我第一次通过Kotlin和Compose来实现一个Canvas时, 我收获了什么?

    自从2019年Google推荐Kotlin为Android开发的首选语言以来已经经历了将近四年的时间, Compose的1.0版本也发布了将近2年的时间, Kotlin+Compose在现阶段的Android开发过程中还远远达不到主流的程度. 我们是否应该开始尝试这个组合? 这个组合有会给我们带来什么? 对于我来说, 我是个守旧又

    2023年04月27日
    浏览(34)
  • 【入门/小白向】第一次在Linux/Ubuntu终端上使用Git拉取代码,该怎么做?保姆教程,步骤分解。

    【Step.1】 安装 git 安装完成后执行下句,可以看到安装版本: 【Step.2】 配置邮箱 (git网站账户注册的邮箱,如bob2023@yy.com) 和用户名 (任取,如bob): 随后可执行下句,查看是否配置成功: 实例执行如下图:  【 Step.3 】生成 SSH 密钥,用于远程访问 git (下面使用的公钥算法是

    2024年02月05日
    浏览(51)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包