来源:ifeve.com/how-to-calculate-threadpool-size/

在我们日常业务开发过程中,或多或少都会用到并发的功能。那么在用到并发功能的过程中,就肯定会碰到下面这个问题

并发线程池到底设置多大呢?

通常有点年纪的程序员或许都听说这样一个说法 (其中 N 代表 CPU 的个数)

  1. CPU 密集型应用,线程池大小设置为 N + 1

  2. IO 密集型应用,线程池大小设置为 2N

这个说法到底是不是正确的呢?

其实这是极不正确的。那为什么呢?

首先我们从反面来看,假设这个说法是成立的,那我们在一台服务器上部署多少个服务都无所谓了。因为线程池的大小只能服务器的核数有关,所以这个说法是不正确的。那具体应该怎么设置大小呢?

假设这个应用是两者混合型的,其中任务即有 CPU 密集,也有 IO 密集型的,那么我们改怎么设置呢?是不是只能抛硬盘来决定呢?

那么我们到底该怎么设置线程池大小呢?有没有一些具体实践方法来指导大家落地呢?让我们来深入地了解一下。

Little's Law(利特尔法则)

一个系统请求数等于请求的到达率与平均每个单独请求花费的时间之乘积

假设服务器单核的,对应业务需要保证请求量(QPS):10 ,真正处理一个请求需要 1 秒,那么服务器每个时刻都有 10 个请求在处理,即需要 10 个线程

同样,我们可以使用利特尔法则(Little’s law)来判定线程池大小。我们只需计算请求到达率和请求处理的平均时间。然后,将上述值放到利特尔法则(Little’s law)就可以算出系统平均请求数。估算公式如下

*线程池大小 = ((线程 IO time + 线程 CPU time )/线程 CPU time ) CPU数目**

具体实践

通过公式,我们了解到需要 3 个具体数值

  1. 一个请求所消耗的时间 (线程 IO time + 线程 CPU time)

  2. 该请求计算时间 (线程 CPU time)

  3. CPU 数目

请求消耗时间

Web 服务容器中,可以通过 Filter 来拦截获取该请求前后消耗的时间

public class MoniterFilter implements Filter {	

    private static final Logger logger = LoggerFactory.getLogger(MoniterFilter.class);	

    @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
long start = System.currentTimeMillis(); HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String uri = httpRequest.getRequestURI();
String params = getQueryString(httpRequest); try {
chain.doFilter(httpRequest, httpResponse);
} finally {
long cost = System.currentTimeMillis() - start;
logger.info("access url [{}{}], cost time [{}] ms )", uri, params, cost);
} private String getQueryString(HttpServletRequest req) {
StringBuilder buffer = new StringBuilder("?");
Enumeration<String> emParams = req.getParameterNames();
try {
while (emParams.hasMoreElements()) {
String sParam = emParams.nextElement();
String sValues = req.getParameter(sParam);
buffer.append(sParam).append("=").append(sValues).append("&");
}
return buffer.substring(0, buffer.length() - 1);
} catch (Exception e) {
logger.error("get post arguments error", buffer.toString());
}
return "";
}
}

CPU 计算时间

CPU 计算时间 = 请求总耗时 - CPU IO time

假设该请求有一个查询 DB 的操作,只要知道这个查询 DB 的耗时(CPU IO time),计算的时间不就出来了嘛,我们看一下怎么才能简洁,明了的记录 DB 查询的耗时。

通过(JDK 动态代理/ CGLIB)的方式添加 AOP 切面,来获取线程 IO 耗时。代码如下,请参考:

public class DaoInterceptor implements MethodInterceptor {	

    private static final Logger logger = LoggerFactory.getLogger(DaoInterceptor.class);	

    @Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch watch = new StopWatch();
watch.start();
Object result = null;
Throwable t = null;
try {
result = invocation.proceed();
} catch (Throwable e) {
t = e == null ? null : e.getCause();
throw e;
} finally {
watch.stop();
logger.info("({}ms)", watch.getTotalTimeMillis()); } return result;
} }

CPU 数目

逻辑 CPU 个数 ,设置线程池大小的时候参考的 CPU 个数

cat /proc/cpuinfo| grep "processor"| wc -l

总结

合适的配置线程池大小其实很不容易,但是通过上述的公式和具体代码,我们就能快速、落地的算出这个线程池该设置的多大。

不过最后的最后,我们还是需要通过压力测试来进行微调,只有经过压测测试的检验,我们才能最终保证的配置大小是准确的。

- END -

关注Java技术栈微信公众号,在后台回复关键字:Java,可以获取一份栈长整理的 Java 最新技术干货。

最近干货分享

公司不用 Spring Boot,果断离职了!

Java 12 骚操作, switch居然还能这样玩!

Java 12 骚操作, String居然还能这样玩

Spring Boot 1.x 正式退役,2.x大步向前

分享一份 2019 最新 Java 架构师学习资料

点击「阅读原文」加入栈长的战队~

到底如何设置 Java 线程池的大小?的更多相关文章

  1. 使用Java 线程池的利弊及JDK自带六种创建线程池的方法

    1. 为什么使用线程池 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到达服务器,这种方式可能是通过网络协 ...

  2. Java线程池ThreadPoolExector的源码分析

    前言:线程是我们在学习java过程中非常重要的也是绕不开的一个知识点,它的重要程度可以说是java的核心之一,线程具有不可轻视的作用,对于我们提高程序的运行效率.压榨CPU处理能力.多条线路同时运行等 ...

  3. java 线程池(1)

    问题 : 线程池中的 coreSize 和 maxSize 的作用分别是什么? 未执行的线程池存在在哪种数据类型,为什么使用这种类型的数据结构 ThreadPoolExecutor概述 ThreadP ...

  4. 深入理解java线程池—ThreadPoolExecutor

    几句闲扯:首先,我想说java的线程池真的是很绕,以前一直都感觉新建几个线程一直不退出到底是怎么实现的,也就有了后来学习ThreadPoolExecutor源码.学习源码的过程中,最恶心的其实就是几种 ...

  5. Java线程池学习总结

    一 使用线程池的好处 池化技术相比大家已经屡见不鲜了,线程池.数据库连接池.Http 连接池等等都是对这个思想的应用.池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率. 线程池提供了 ...

  6. 一次Java线程池误用(newFixedThreadPool)引发的线上血案和总结

    一次Java线程池误用(newFixedThreadPool)引发的线上血案和总结 这是一个十分严重的线上问题 自从最近的某年某月某天起,线上服务开始变得不那么稳定(软病).在高峰期,时常有几台机器的 ...

  7. Java线程池解析

    Java的一大优势是能完成多线程任务,对线程的封装和调度非常好,那么它又是如何实现的呢? jdk的包下和线程相关类的类图. 从上面可以看出Java的线程池主的实现类主要有两个类ThreadPoolEx ...

  8. java 线程池——异步任务

    一.简单粗暴的线程 最原始的方式,当我们要并行的或者异步的执行一个任务的时候,我们会直接使用启动一个线程的方式,如下面所示: new Thread(new Runnable() { @Override ...

  9. Java 线程池框架核心代码分析--转

    原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...

随机推荐

  1. Vscode窗口被覆盖

    用Vscode用的好好的,突然就发现窗口被覆盖了 解决办法如下:1.ctrl+shift+p 快捷键打开如下界面, 2.接着在搜索框中输入settings,点击首先项:打开设置(json) 3.再se ...

  2. CSP2019 前 随感

    因为博主并没有任何的 oi 水平.文化课水平以及作文水平,下面的东西都是对辞藻和古诗词的堆砌. 不知不觉又到了新一年的 noip 了. 好像是去年的双十一的晚上,noip 考挂的我绝望地写了 bzoj ...

  3. varnish流程图

    varnish流程图

  4. Jdbc连接数据库基本步骤详解_java - JAVA

    文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 Jdbc连接数据库的基本步骤,供大家参考,具体内容如下 package demo.jdbc; import java.s ...

  5. 【leetcode】1147. Longest Chunked Palindrome Decomposition

    题目如下: Return the largest possible k such that there exists a_1, a_2, ..., a_k such that: Each a_i is ...

  6. PHP入门培训教程 PHP变量的使用

      很多朋友在编写PHP程序的时候有时候对变量总有着不能确定的问题,而且也有很多问题就是因为变量的处理不当所造成的.这里兄弟连PHP培训 小编,就PHP变量系统说一下. PHP的变量分为全局变量与局部 ...

  7. qt学习(一)qt三个文件函数的框架

    学到点什么, 而不是复制着什么, 每天敲着别人给的代码,苦涩得改完bug, 就这样一天天的过去, 实质上并没有学到什么, 别人的思想只是拿来借鉴, 你的思想是好是坏都是你的, 不用急着抛弃自己. 从q ...

  8. HDU - 6578 Blank DP + 滚动数组

    HDU - 6578 Blank 题意 给你\(\{0,1,2, 3\}\)四个数,分别填入长度为\(n\)的数列中,有\(m\)个限制条件,\(l_{i}, r_{i}, x_{i}\)表示在\([ ...

  9. AGC030F - Permutation and Minimum

    https://atcoder.jp/contests/agc030/tasks/agc030_f 题解 我们先把这个排列从\(1 \sim 2n\)表达出来,然后题面中的每一对数我们可以用一条线把他 ...

  10. [CF1082G]Petya and Graph:最小割

    分析 发这篇博客的目的就是要让你们知道博主到底有多菜. 类似于[NOI2006]最大获利.(明明就是一模一样好吧!) 不知道怎么了,半秒就想到用网络流,却没想出怎么建图. 连这么简单的题都没做出来,我 ...