首页 > 编程语言 >Java中线程池的最佳实践

Java中线程池的最佳实践

时间:2024-07-11 17:59:26浏览次数:13  
标签:程池 Java 队列 线程 关闭 new 中线 ThreadPoolExecutor threadPoolExecutor

一、使用正确的声明方式

线程池必须手动通过ThreadPoolExecutor 的构造函数来声明,避免使用Executors类创建线程池,会有 OOM 风险。

Executors创建的线程池对象有以下弊端:

  1. FixedThreadPoolSingleThreadExecutor使用的是有界阻塞队列LinkedBlockingQueue,任务队列的默认长度和最大长度为Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM
  2. CachedThreadPool使用的是同步队列SysnchronousQueue,允许创建的线程数量为最大长度为Integer.MAX_VALUE,可能导致会创建大量线程,从而导致OOM
  3. ScheduledThreadPool SingleThreadScheduledExecutor : 使用的无界的延迟阻塞队列DelayedWorkQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

说白了就是,使用有界队列,然后控制线程创建数量

二、不同业务使用不同的线程池

很多人在实际项目中都会有类似这样的问题:我的项目中多个业务需要用到线程池,是为每个线程池都定义一个还是说定义一个公共的线程池呢?

一般建议是不同的业务使用不同的线程池,配置线程池的时候根据当前业务的情况对当前线程池进行配置,因为不同的业务的并发以及对资源的使用情况都不同,重心优化系统性能瓶颈相关的业务

三、监控线程池的状态

你可以通过一些手段来检测线程池的运行状态比如 SpringBoot 中的 Actuator 组件。

除此之外,我们还可以利用 ThreadPoolExecutor 的相关 API 做一个简陋的监控。从下图可以看出, ThreadPoolExecutor提供了获取线程池当前的线程数和活跃线程数、已经执行完成的任务数、正在排队中的任务数等等。
在这里插入图片描述

四、线程池的正确命名

初始化线程池的时候需要显示命名(设置线程池名称前缀),有利于定位问题。

默认情况下创建的线程名字类似 pool-1-thread-n 这样的,没有业务含义,不利于我们定位问题。

/**
 * 自定义线程工厂,用来自定义线程池中的命名
 */
public class CustomThreadFactory implements ThreadFactory {
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String threadNamePrefix;

    public CustomThreadFactory(String threadNamePrefix) {
        this.threadNamePrefix = threadNamePrefix;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, threadNamePrefix + "-" + threadNumber.getAndIncrement());
        return t;
    }
}

创建时调用

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(QUEUE_CAPACITY), // 有界阻塞队列
                new CustomThreadFactory("custom-thread-pool"),
                new ThreadPoolExecutor.CallerRunsPolicy());

五、正确配置线程池参数

线程数更严谨的计算的方法应该是:最佳线程数 = N(CPU 核心数)∗(1+WT(线程等待时间)/ST(线程计算时间)),其中 WT(线程等待时间)=线程运行总时间 - ST(线程计算时间)

也可以使用一些开源项目

  • Hippo4j:异步线程池框架,支持线程池动态变更&监控&报警,无需修改代码轻松引入。支持多种使用模式,轻松引入,致力于提高系统运行保障能力
  • Dynamic :轻量级动态线程池,内置监控告警功能,集成三方中间件线程池管理,基于主流配置中心(已支持 Nacos、Apollo,Zookeeper、Consul、Etcd,可通过 SPI 自定义实现)

六、正确关闭线程池

线程池提供了两个关闭方法:

  • shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。
  • shutdownNow() :关闭线程池,线程池的状态变为 STOP。线程池会终止当前正在运行的任务,停止处理排队的任务并返回正在等待执行的 List。

调用完 shutdownNow 和 shuwdown 方法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。
如果要同步等待线程池彻底关闭后才继续往下执行,需要调用awaitTermination方法进行同步等待。

// ...
// 关闭线程池
executor.shutdown();
try {
    // 等待线程池关闭,最多等待5分钟
    if (!executor.awaitTermination(5, TimeUnit.MINUTES)) {
        // 如果等待超时,则打印日志
        System.err.println("线程池未能在5分钟内完全关闭");
    }
} catch (InterruptedException e) {
    // 异常处理
}

七、Spring中使用线程池的配置参考

@Configuration
@EnableAsync
public class ThreadPoolExecutorConfig {

    @Bean(name="threadPoolExecutor")
    public Executor threadPoolExecutor(){
        ThreadPoolTaskExecutor threadPoolExecutor = new ThreadPoolTaskExecutor();
        int processNum = Runtime.getRuntime().availableProcessors(); // 返回可用处理器的Java虚拟机的数量
        int corePoolSize = (int) (processNum / (1 - 0.2));
        int maxPoolSize = (int) (processNum / (1 - 0.5));
        threadPoolExecutor.setCorePoolSize(corePoolSize); // 核心池大小
        threadPoolExecutor.setMaxPoolSize(maxPoolSize); // 最大线程数
        threadPoolExecutor.setQueueCapacity(maxPoolSize * 1000); // 设置线程池任务队列的容量为最大线程数的1000倍
        threadPoolExecutor.setThreadPriority(Thread.MAX_PRIORITY); //设置线程池中线程的优先级为最高
        threadPoolExecutor.setDaemon(false); //设置线程池中的线程不是守护线程。这意味着应用程序不会在所有非守护线程结束时立即退出
        threadPoolExecutor.setKeepAliveSeconds(300);// 线程空闲时间
        threadPoolExecutor.setThreadNamePrefix("test-Executor-"); // 线程名字前缀
        return threadPoolExecutor;
    }
}

标签:程池,Java,队列,线程,关闭,new,中线,ThreadPoolExecutor,threadPoolExecutor
From: https://blog.csdn.net/weixin_43848975/article/details/140357286

相关文章

  • Java多线程&并发编程(二)
    一、CyclicBarrier、CountDownLatch、Semaphore的区别CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行(类似于一个栅栏拦住所有线程直到所有线程到达后在重新执行)CountDownLatch则不是,某线程运行到某个点上之后,......
  • 基于Javaweb在线手机购物商城系统设计与实现
      博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩。项目配有对应开发文档、开题报告、任务书......
  • java 生成mapbox-gl 可以直接使用的雪碧图,包含对应json,图片大小无限制自动适配
    1、文件路径配置sprite-path:/home/mapplate/sprite/2、实现类packagecom.shgis.service.impl;/***CreatedbyAdministratoron2021/10/9.*/importcom.alibaba.fastjson.JSONObject;importcom.shgis.config.FileProperties;importcom.shgis.entity.Ebuf......
  • JavaScript 进阶(五)---forEach/map/filterevery/some/includes/reduce的详细用法
    目录1.forEach2.map3.filter4.for...in5.for...of6.every7.some8.includes9.reduce举个例子:使用fliter:使用 map 来筛选并转换数组使用 forEach 来筛选并构建数组总结1.forEach-详解:`forEach`方法对数组的每个元素执行一次提供的函数。这个方......
  • Java毕业设计基于Vue+SpringBoot的高校学生评教系统(代码+数据库+文档LW+运行成功)
    文末获取资源,收藏关注不迷路文章目录项目介绍技术介绍项目界面关键代码目录项目介绍当今社会己进入信息社会时代。信息己经受到社会的广泛关注,被看作社会和科学技术发展的三大支柱;材料、能源、信息;。信息是管理的基础,是进行决策的的基本依据。在一个组织里,信息......
  • Java毕业设计基于Vue+SpringBoot的癌症患者交流平台(代码+数据库+文档LW+运行成功)
    文末获取资源,收藏关注不迷路文章目录项目介绍技术介绍项目界面关键代码目录项目介绍随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。癌症患者交流平台,主要的模块包括查看首页、轮播图、抗癌故事管理......
  • 不用JavaScript实现鼠标移入判断示例
    要点利用了伪元素生成了4个三角形组成了一个正方形,通过hover哪个透明的三角形来判断用户的操作方位。具体实现HTML:<divclass="box"><divclass="box__right">Right→Left</div><divclass="box__left">Left→Right</div><divclas......
  • Java异步判断线程池所有任务是否执行完成的方法
    1.使用ExecutorService和CountDownLatch的方法示例在Java中,当我们使用线程池(如ExecutorService)来执行异步任务时,常常需要知道所有任务是否都已经完成。ExecutorService接口提供了几种方式来处理这种情况,但最常用的是shutdown()和awaitTermination()方法的组合,或者使用Future和Com......
  • 【java】实现sse调用websocket接口,忽略wss证书并控制sse吐字速度
    maven<dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.5.3</version></dependency>AsyncConfigpackag......
  • JAVA生成验证码(字母+数字)
    /***验证码、邀请码工具类*/@ComponentpublicclassVerificationCodeUtil{/***生成验证码:字母+数字*@paramlength验证码长度*@return验证码字符串*/publicstaticStringgetVerificationCode(intlength){Str......