Java并发编程实战
王宝令
资深架构师
立即订阅
14233 人已学习
课程目录
已完结 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你为什么需要学习并发编程?
免费
学习攻略 (1讲)
学习攻略 | 如何才能学好并发编程?
第一部分:并发理论基础 (13讲)
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
第二部分:并发工具类 (14讲)
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
第三部分:并发设计模式 (10讲)
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
第四部分:案例分析 (4讲)
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
第五部分:其他并发模型 (4讲)
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
结束语 (1讲)
结束语 | 十年之后,初心依旧
用户故事 (2讲)
用户来信 | 真好,面试考到这些并发编程,我都答对了!
3 个用户来信 | 打开一个新的并发世界
Java并发编程实战
登录|注册

10 | Java线程(中):创建多少线程才是合适的?

王宝令 2019-03-21

在 Java 领域,实现并发程序的主要手段就是多线程,使用多线程还是比较简单的,但是使用多少个线程却是个困难的问题。工作中,经常有人问,“各种线程池的线程数量调整成多少是合适的?”或者“Tomcat 的线程数、Jdbc 连接池的连接数是多少?”等等。那我们应该如何设置合适的线程数呢?

要解决这个问题,首先要分析以下两个问题:

  1. 为什么要使用多线程?
  2. 多线程的应用场景有哪些?

为什么要使用多线程?

使用多线程,本质上就是提升程序性能。不过此刻谈到的性能,可能在你脑海里还是比较笼统的,基本上就是快、快、快,这种无法度量的感性认识很不科学,所以在提升性能之前,首要问题是:如何度量性能。

度量性能的指标有很多,但是有两个指标是最核心的,它们就是延迟和吞吐量。延迟指的是发出请求到收到响应这个过程的时间;延迟越短,意味着程序执行得越快,性能也就越好。 吞吐量指的是在单位时间内能处理请求的数量;吞吐量越大,意味着程序能处理的请求越多,性能也就越好。这两个指标内部有一定的联系(同等条件下,延迟越短,吞吐量越大),但是由于它们隶属不同的维度(一个是时间维度,一个是空间维度),并不能互相转换。

我们所谓提升性能,从度量的角度,主要是降低延迟,提高吞吐量。这也是我们使用多线程的主要目的。那我们该怎么降低延迟,提高吞吐量呢?这个就要从多线程的应用场景说起了。

© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(92)

  • CHEN川
    更多的精力其实应该放在算法的优化上,线程池的配置,按照经验配置一个,随时关注线程池大小对程序的影响即可,具体做法:可以为你的程序配置一个全局的线程池,需要异步执行的任务,扔到这个全局线程池处理,线程池大小按照经验设置,每隔一段时间打印一下线程池的利用率,做到心里有数。

    看到过太多的代码,遇到要执行一个异步任务就创建一个线程池,导致整个程序的线程池大到爆,完全没必要。而且大多数时候,提高吞吐量可以通过使用缓存、优化业务逻辑、提前计算好等方式来处理,真没有必要太过于关注线程池大小怎么配置,如果小了就改大一点,大了改小一点就好,从老师本文的篇幅也可以看出来。

    经验值不靠谱的另外一个原因,大多数情况下,一台服务器跑了很多程序,每个程序都有自己的线程池,那CPU如何分配?还是根据实际情况来确定比较好。
    2019-03-21
    3
    56
  • 多拉格·five
    问一下老师,这个线程配置比我在其他的资料也看过,但是最后那个公式没见过,方便说一下如何测试IO/CPU 这个耗时比例吗

    作者回复: 比较简单的工具就是apm了

    2019-03-21
    19
  • 不靠谱的琴谱
    如果我一个cpu是4核8线程 这里线程数数量是4+1还是8+1(cpu密集类型)
    2019-03-21
    3
    16
  • 假行僧
    个人觉得公式话性能问题有些不妥,定性的io密集或者cpu密集很难在定量的维度上反应出性能瓶颈,而且公式上忽略了线程数增加带来的cpu消耗,性能优化还是要定量比较好,这样不会盲目,比如io已经成为了瓶颈,增加线程或许带来不了性能提升,这个时候是不是可以考虑用cpu换取带宽,压缩数据,或者逻辑上少发送一些。最后一个问题,我的答案是大部分应用环境是合理的,老师也说了是积累了一些调优经验后给出的方案,没有特殊需求,初始值我会选大家都在用伪标准

    作者回复: 👍👍

    2019-03-21
    15
  • aksonic
    早起的鸟果然有食吃,抢到了顶楼,哈哈。
    对于老师的思考题,我觉得不合理,本来就是分CPU密集型和IO密集型的,尤其是IO密集型更是需要进行测试和分析而得到结果,差别很大,比如IO/CPU的比率很大,比如10倍,2核,较佳配置:2*(1+10)=22个线程,而2*CPU核数+1 = 5,这两个差别就很大了。老师,我说的对不对?

    作者回复: 不但起的早,还看懂了

    2019-03-21
    11
  • 董宗磊
    思考题:认为不合理,不能只考虑经验,还有根据是IO密集型或者是CPU密集型,具体问题具体分析。
    看今天文章内容,分享个实际问题;我们公司服务器都是容器,一个物理机分出好多容器,有个哥们设置线程池数量直接就是:Runtime.getRuntime().availableProcessors() * 2;本来想获取容器的CPU数量 * 2,其实Runtime.getRuntime().availableProcessors()获取到的是物理机CPU合数,一下开启了好多线程 ^_^

    作者回复: 新版的jvm开始支持docker了,老版本问题还挺多

    2019-03-21
    8
  • 探索无止境
    老师早上好,当应用来的请数量过大,此时线程池的线程已经不够使用,排队的队列也已经满了,那么后面的请求就会被丢弃掉,如果这是一个更新数据的请求操作,那么就会出现数据更新丢失,老师有没有什么具体的解决思路?期待解答

    作者回复: 单机有瓶颈,就分布式。
    数据库有瓶颈,就分库分表分片

    2019-03-21
    7
  • QQ怪
    我就想问下如何测试io耗时和cpu耗时

    作者回复: apm工具可以

    2019-03-21
    4
  • 阿冲
    老师,你好!有个疑惑就是我在写web应用的时候一般都是一个请求里既包含cpu计算(比如字符串检验)又包含操作(比如数据库操作),这种操作就是一个线程完成的。那么这种情况按你写的这个公式还起作用吗?c#里面有对io操作基本都封装了异步方法,很容易解决我刚说的问题(调用异步方法就会切换线程进行io操作,等操作完了再切回来)。java要达到这种效果代码一般怎么写比较合适?

    作者回复: 就是针对一个线程既有cpu也有io的,这个才是io密集型

    2019-03-21
    1
    4
  • 陈华应
    理论加经验加实际场景,比如现在大多数公司的系统是以服务的形式来通过docker部署的,每个docker服务其实对应部署的就一个服务,这样的情况下是可以按照理论为基础,再加上实际情况来设置线程池大小的,当然通过各种监控来调整是最好的,但是实际情况是但服务几十上百,除非是核心功能,否则很难通过监控指标来调整线程池大小。理论加经验起码不会让设置跑偏太多,还有就是服务中的各种线程池统一管理是很有必要的

    作者回复: 说的太对了!!!

    2019-03-31
    3
  • Weixiao
    最佳线程数 =1 +(I/O 耗时 / CPU 耗时),

    文中说,1表示一个线程执行io,另外R个线程刚好执行完cpu计算。

    这里理解有点问题,这个公式是按照单核给出的,所以不可能存在同时R个线程执行cpu计算。所以我理解文章中说反了,应该是1个线程在执行cpu,然后有R个线程可以同时在执行io,这样cpu的利用率为100%

    作者回复: 你对照着图理解一下,cpu时间上没有重叠

    2019-03-24
    1
    3
  • 已忘二
    老师,有个疑问,就是那个I/O和CPU比为2:1时,CPU使用率达到了100%,但是I/O使用率却到了200%,也就是时刻有两个I/O同时执行,这样是可以的么?I/O不需要等待的么?

    作者回复: io有瓶颈后,cpu使用率就上不去了

    2019-03-21
    3
  • zsh0103
    请问老师,
    1 在现实项目如何计算I/O耗时与CPU耗时呢,比如程序是读取网络数据,然后分析,最后插入数据库。这里网络读取何数据库插入是两次IO操作,计算IO耗时是两次的和吗?
    2. 如果我在一台机器上部署2个服务,那计算线程数是要每个服务各占一半的数量吗?
    3. 如果我用一个8核CPU的机器部署服务,启动8个不同端口的相同服务,和启动一个包含8个线程的服务在处理性能上会有区别吗?

    作者回复: 1.两次之和
    2.理论值仅仅适用部署一个服务的场景。
    3.有区别

    2019-03-21
    3
  • Geek_Zjy
    我有个疑惑哈,老师的算法都是以 CPU 核数为参数,但是在硬件上有这种情况:
    比如Intel 赛扬G460是单核心,双线程的CPU,
    Intel 酷睿i3 3220是双核心 四线程,
    Intel 酷睿i5 4570是四核心 四线程,
    Intel 酷睿i7 4770K是四核心 八线程 等等
    这个对那个算法有影响吗?
    还有就是线程让出CPU内核 时,他的数据是要刷新到内存中保存吗?(我不是想要挑刺啊,我是觉得这个和前面讲的可见性应该有关系,比如单核多线程是不是不会有可见性的问题?当然只要是单线程不管多少核则定没有可见性的问题)
    @董宗磊 提到了 availableProcessors ,这个我看文档写的是获得可用的Java虚拟机的可用的处理器数量,和实际的主机 CPU 核数不是一致的吧,先忽略 docker 的问题,它可以用作算法中的 CPU核数吗?(我对“可用”俩字也很迷惑,难道这个数量会动态变化吗?)

    作者回复: 用逻辑核数就行,也就是操作系统里看到的核数

    2019-07-19
    2
  • 榣山樵客™
    在4核8线程的处理器使用Runtime.availableProcessors()结果是8,超线程技术属于硬件层面上的并发,从cpu硬件来看是一个物理核心有两个逻辑核心,但因为缓存、执行资源等存在共享和竞争,所以两个核心并不能并行工作。超线程技术统计性能提升大概是30%左右,并不是100%。另外,不管设置成4还是8,现代操作系统层面的调度应该是按逻辑核心数,也就是8来调度的(除非禁用超线程技术)。所以我觉得这种情况下,严格来说,4和8都不一定是合适的,具体情况还是要根据应用性能和资源的使用情况进行调整。这是个人的理解,请老师指正。

    作者回复: 工作中都是按照逻辑核数来的,理论值和经验值只是提供个指导,实际上还是要靠压测。

    2019-03-22
    2
  • 马晓光
    实际项目中怎么确定IO耗时、CPU耗时?

    作者回复: apm工具可以精确到方法耗时,io相关的方法一般是知道的

    2019-03-22
    2
  • 曾轼麟
    老师我记得csapp那本书中说过,x86架构的CPU是拥有超程技术的,也就是一个核可以当成两个使用,AMD的却没有,不知道您的这个计算公式是否适合其它厂商的CPU呢?

    作者回复: 都按照逻辑核数设置,最终还是要根据压测数据调整的

    2019-03-22
    2
  • 狂战俄洛伊
    对于这个思考题,我觉得是比较合理。
    因为经验是经过大量实践的结果,是符合大多数的情况,而且是一种快速估计的方法。
    我看留言区里很多都说不合理,并且给出了例子。我觉得他们说的也没错,只是举出了经验没覆盖到的情况而已。
    这里我还有个疑问,这篇文章中都是在讲一台机器工作的情况下。我想问的是如果是在一个集群里,这个线程数又该怎么计算?
    例如有三台机器构成一个集群,这三台机器的cpu分别是8核,4核,2核。就打算是cpu密集型,这时候该怎么计算线程数?

    作者回复: 每台机器算自己的,发挥出每台机器的硬件能力就可以了

    2019-03-21
    2
  • walkingonair
    当I/O 耗时远远大于CPU耗时时,"2 * CPU 的核数 + 1"会导致所有线程在长时间下都处于等待I/O操作的状态,而无法合理利用CPU
    2019-03-21
    2
  • 姜戈
    2*CPU核数+1,我觉得不合理,针对IO密集型,老师提供的公式是:CPU核数*(1+IO耗时/CPU耗时)。2*CPU核数+1这个公式相当于这里有个潜在估计,假设了IO消耗时间与CPU消耗时间1:1,再加一个线程用来预防其中有某个线程被阻塞,及时顶上。针对IO密集型,要考虑的就是IO耗时与CPU耗时之比!这个经验公式只是针对其中1:1耗时比一种情况,不够全面!
    2019-03-21
    2
收起评论
92
返回
顶部