浅谈踩坑记之一个Java线程池参数,差点引起线上事故(下)

简介: 浅谈踩坑记之一个Java线程池参数,差点引起线上事故

三、 关于线程池配置

线程池配置非常重要,但是往往很容易忽视,配置不合理或者线程池复用次数少,依然会频繁的创建和销户。

1.如何合理计算核心线程数?

我们可以通过接口平均响应时间和服务需要支撑的QPS计算 例如: 我们接口平均RT 0.005s,那么,一个工作线程可以处理任务数200 如果单机需要支撑QPS 3W,那么可以计算出 需要核心线程数 150

即公式: QPS ➗ (1 ➗ 平均RT) = QPS * RT

2.容易忽视的 @Async 注解

Spring中使用 @Async 注解 默认线程池是 SimpleAsyncTaskExecutor,默认情况下如果没有配置等于没有使用线程池,因为它每次都会重新创建一个新的线程,不会复用。

所以切记,如果使用@Async 一定要配置.


@EnableAsync
@Configuration
@Slf4j
public class ThreadPoolConfig {
    private static final int corePoolSize = 100;             // 核心线程数(默认线程数)
    private static final int maxPoolSize = 400;             // 最大线程数
    private static final int keepAliveTime = 60;            // 允许线程空闲时间(单位:默认为秒)
    private static final int queueCapacity = 0;         // 缓冲队列数
    private static final String threadNamePrefix = "Async-Service-"; // 线程池名前缀
    @Bean("taskExecutor") 
    public ThreadPoolTaskExecutor getAsyncExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveTime);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

四、 线程池飙升如何引起的?

Dubbo服务端工作线程我们配置如下:

corethreads: 150
threads: 800
threadpool: cached
queues: 10

看上去是不是挺合理的,设置很小队列数,是为了防止抖动引起短暂线程池不足情况。从上面看,貌似也没什么问题,从白天业务量来说核心线程数是完全够用的(RT<5ms, QPS<1w)。可是上线之后,线程池一路飙升,最大达到阈值最大值800, 报警信息如下:

org.apache.dubbo.remoting.RemotingException("Server side(IP,20880) thread pool is exhausted, detail msg:Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-IP:20880, Pool Size: 800 (active: 4, core: 300, max: 800, largest: 800), Task: 4101304 (completed: 4101301), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://IP:20880!"

从上可以看出,到达最大线程数时,active线程数是很少的,这完全不符合预期。


五、场景模拟

由源码

 queues == 0 ? new SynchronousQueue<Runnable>() :
                        (queues < 0 ? new LinkedBlockingQueue<Runnable>()
                                : new LinkedBlockingQueue<Runnable>(queues))

可知:

当队列元素为0时,阻塞队列使用的是SynchronousQueue;当队列元素小于0时,使用的是无界阻塞队列LinkedBlockingQueue;当队列元素大于0时,使用的是有界的队列LinkedBlockingQueue。

核心线程数和最大线程数肯定不会有问题,所以我猜想是否队列数设置是否有问题。

为了复现,我写了个简单的代码模拟

package com.bytearch.fast.cloud;
import java.util.concurrent.*;
public class TestThreadPool {
    public final static int queueSize = 10;
    public static void main(String[] args) {
        ExecutorService executorService = getThreadPool(queueSize);
        for (int i = 0; i < 100000; i++) {
            int finalI = i;
            try {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        doSomething(finalI);
                    }
                });
            } catch (Exception e) {
                System.out.println("emsg:" + e.getMessage());
            }
            if (i % 20 == 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("all done!");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static ExecutorService getThreadPool(int queues) {
        int cores = 150;
        int threads = 800;
        int alive = 60 * 1000;
        return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue<Runnable>() :
                        (queues < 0 ? new LinkedBlockingQueue<Runnable>()
                                : new LinkedBlockingQueue<Runnable>(queues)));
    }
    public static void doSomething(final int i) {
        try {
            Thread.sleep(5);
            System.out.println("thread:" + Thread.currentThread().getName() +  ", active:" + Thread.activeCount() + ", do:" + i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

模拟结果:

queueSize值 现象
0 没有出现异常
10 出现拒绝异常
100 没有出现异常

异常如下:

emsg:Task com.bytearch.fast.cloud.TestThreadPool$1@733aa9d8 rejected from java.util.concurrent.ThreadPoolExecutor@6615435c[Running, pool size = 800, active threads = 32, queued tasks = 9, completed tasks = 89755]
all done!

很显然,当并发较高时,使用LinkedBlockingQueue有界队列, 队列数设置相对较小时,线程池会出现问题。

将queues配置改为0后上线,恢复正常。

至于更深层原因,感兴趣同学可以深度分析下,也可以后台与我交流。


六、总结

这次分享了线程池 ThreadPoolExecutor 基本原理、线程池配置计算方式,和容易忽视的使用注解@Async 配置问题。

另外介绍了下我们使用线程池遇到的诡异问题,一个参数问题,可能导致不可预期的后果。

希望以上分享对你们有所帮助。

相关文章
|
1月前
|
缓存 Java
线程池的核心参数
线程池七大参数解析:核心线程数决定常驻线程,最大线程数控制并发上限,存活时间管理非核心线程生命周期,工作队列缓存待处理任务,线程工厂定制线程属性,拒绝策略应对任务过载,提升系统稳定性与资源利用率。
227 1
|
Java 存储
线程池的核心参数有哪些?
线程池七大核心参数:核心/最大线程数、线程保持时间及单位、阻塞队列、线程工厂与拒绝策略。
509 79
|
4月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
188 0
|
7月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
354 60
【Java并发】【线程池】带你从0-1入门线程池
|
6月前
|
Java Linux 定位技术
Minecraft配置文件参数说明(JAVA服务器篇)
Minecraft JAVA版服务器启动后会生成server.properties配置文件,位于minecraft_server/根目录下。该文件包含多项关键设置,如游戏模式(gamemode)、最大玩家数(max-players)、难度(difficulty)等。此文档详细说明了各配置项的功能与默认值,帮助用户高效管理服务器环境。
1564 60
|
5月前
|
Java
java中一个接口A,以及一个实现它的类B,一个A类型的引用对象作为一个方法的参数,这个参数的类型可以是B的类型吗?
本文探讨了面向对象编程中接口与实现类的关系,以及里氏替换原则(LSP)的应用。通过示例代码展示了如何利用多态性将实现类的对象传递给接口类型的参数,满足LSP的要求。LSP确保子类能无缝替换父类或接口,不改变程序行为。接口定义了行为规范,实现类遵循此规范,从而保证了多态性和代码的可维护性。总结来说,接口与实现类的关系天然符合LSP,体现了多态性的核心思想。
118 0
|
6月前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
283 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
6月前
|
Java
线程池的核心参数有哪些 ?
corePoolSize 核心线程数量 maximumPoolSize 最大线程数量 keepAliveTime 线程保持时间,N个时间单位 unit 时间单位(比如秒,分) workQueue 阻塞队列 threadFactory 线程工厂 handler 线程池拒绝策略
|
8月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
335 17
|
9月前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####