Java 业务开发常见错误 100 例
朱晔
贝壳金服资深架构师
52944 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 48 讲
代码篇 (23讲)
Java 业务开发常见错误 100 例
15
15
1.0x
00:00/00:00
登录|注册

03 | 线程池:业务代码最常用也最容易犯错的组件

需要仔细斟酌线程池的混用策略
务必确认清楚线程池本身是不是复用的
线程池线程管理策略详解
线程池的声明需要手动进行
线程池

该思维导图由 AI 生成,仅供参考

你好,我是朱晔。今天,我来讲讲使用线程池需要注意的一些问题。
在程序中,我们会用各种池化技术来缓存创建昂贵的对象,比如线程池、连接池、内存池。一般是预先创建一些对象放入池中,使用的时候直接取出使用,用完归还以便复用,还会通过一定的策略调整池中缓存对象的数量,实现池的动态伸缩。
由于线程的创建比较昂贵,随意、没有控制地创建大量线程会造成性能问题,因此短平快的任务一般考虑使用线程池来处理,而不是直接创建线程。
今天,我们就针对线程池这个话题展开讨论,通过三个生产事故,来看看使用线程池应该注意些什么。

线程池的声明需要手动进行

Java 中的 Executors 类定义了一些快捷的工具方法,来帮助我们快速创建线程池。《阿里巴巴 Java 开发手册》中提到,禁止使用这些方法来创建线程池,而应该手动 new ThreadPoolExecutor 来创建线程池。这一条规则的背后,是大量血淋淋的生产事故,最典型的就是 newFixedThreadPool 和 newCachedThreadPool,可能因为资源耗尽导致 OOM 问题。
首先,我们来看一下 newFixedThreadPool 为什么可能会出现 OOM 的问题。
我们写一段测试代码,来初始化一个单线程的 FixedThreadPool,循环 1 亿次向线程池提交任务,每个任务都会创建一个比较大的字符串然后休眠一小时:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

线程池管理是开发中常见但容易出现问题的技术点。本文通过三个生产事故案例,深入探讨了线程池的使用注意事项。首先,强调了使用Executors类提供的快捷方法创建线程池可能导致的问题,建议手动声明线程池并根据实际场景评估核心参数。其次,详细分析了newFixedThreadPool和newCachedThreadPool可能导致的OOM问题,强调了设置有界的工作队列和可控的线程数的重要性。作者还分享了一个实际事故案例,强调了为自定义线程池指定有意义的名称以便排查问题的重要性。最后,建议使用监控手段观察线程池状态,以便提早发现并解决问题。文章内容丰富,包括了线程池的默认工作行为、线程池的复用问题等,对读者在实际应用中遇到的问题提供了解决思路和最佳实践。文章通过生动的案例和详细的分析,提醒读者在使用线程池时要慎之又慎,避免常见的陷阱和问题。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 业务开发常见错误 100 例》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(73)

  • 最新
  • 精选
  • 蚂蚁内推+v
    置顶
    第二个问题大家都说核心线程数不会被回收,但是方法执行完线程池的引用已经引用不到了吧,线程池对象会被垃圾回收吧,垃圾回收时核心线程怎么办呢

    作者回复: ThreadPoolExecutor回收不了,可以看看其源码,工作线程Worker是内部类,只要它活着,换句话说线程在跑,就会阻止ThreadPoolExecutor回收,所以其实ThreadPoolExecutor是无法回收的,并不能认为ThreadPoolExecutor没有引用就能回收

    2020-03-14
    7
    28
  • Darren
    置顶
    第一个问题的来了,请老师指点: https://github.com/y645194203/geektime-java-100/blob/master/ExtremeThreadPoolExecutor.java 里面自定义了一个extremeOffer方法,因为不是BlockQUeue接口的方法,所以在执行拒绝策略后,真正加入阻塞队列的时候强转了一些,感觉不是很好,有没有更好的处理方法,请老师指点下。 加上之前回答的第二个问题答案: 不会被回收,会OOM,即使是自定义线程池,核心线程是不会回收的,每次需要10个线程,刚好是核心线程数,因此每次请求都会创建10个核心线程数的线程池,请求次数多了后,很快就回OOM。 Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

    作者回复: 直接用put即可,可以参考这里的回复: https://stackoverflow.com/questions/19528304/how-to-get-the-threadpoolexecutor-to-increase-threads-to-max-before-queueing 不过要考虑选择丢数据还是阻塞 其实,实现自己的RejectedExecutionHandler耦合自己的Queue也无可厚非。Tomcat也是这样的,其实现参考这里:https://github.com/apache/tomcat/blob/a801409b37294c3f3dd5590453fb9580d7e33af2/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java

    2020-03-12
    6
    29
  • 观弈道人
    "我们把 printStats 方法打印出的日志绘制成图表",想问下老师,这个图表咋绘制的,想学习下

    作者回复: Excel...

    2020-03-18
    53
  • 汝林外史
    1. 既然选择先扩容线程池再加入队列,那为什么不干脆把核心线程数设置大一些,然后核心线程数可回收这种策略呢? 2. 复用线程池,任务很慢,主线程get结果的时候不会导致主线程卡死的状态吗?不是也提倡不同的任务用不同的线程池,那复用与不复用的边界在哪里呢?是要根据也无需求自己评估吗?

    作者回复: 1. 你说的这种策略,此文也有提到: https://stackoverflow.com/questions/19528304/how-to-get-the-threadpoolexecutor-to-increase-threads-to-max-before-queueing 其实,我们希望的是尽量确保有足够多的线程能处理任务,但是又不闲置过多线程,或临时创建过多线程,换句话说让线程的创建和回收不要太频繁。选择哪个策略要根据任务的性质和压力的流量形态来决定。 2. 这里我说的复用线程池是指不每次都创建线程池,线程池必须复用而不是按需创建,但是不推荐一味混用一个线程池。对于选择是否混用线程池,至少对于频+快的任务和少+慢的任务应该分开,还是要根据实际任务的性质来选择。

    2020-03-14
    18
  • 陈天柱
    首先赞一下老师的排查问题的思路!!!然后针对第二个问题,我觉得不会被回收且很快就会OOM了,因为每次请求都新建线程池,每个线程池的核心数都是10, 虽然自定义线程池设置2秒回收,但是没超过线程池核心数10是不会被回收的, 不间断的请求过来导致创建大量线程,最终OOM

    作者回复: 👍🏻

    2020-03-12
    4
    9
  • CDz
    1. 线程池过多造成OOM 因为活跃线程过多和线程池不会被回收 2. Java Stream Api异步分流 公用一个默认forkjion线程池,使用时要注意 3. 线程池创建时要分析执行任务是IO资源型还是CPU资源型 4. IO资源型或者说执行较长时间任务,并且拒绝策略为Call时,会在线程池满状态后交给**调用者**线程执行,如果是Web服务跑在tomcat⬆️的话,就导致整体吞吐量下降

    作者回复: 总结不错

    2020-04-18
    8
  • miniluo
    那JDK提供的四种快捷创建线程池的方式时候没有想到这些坑吗?如果想到了为何还提供这些方便的方式呢?还要麻烦老师解答,谢谢~

    作者回复: 提供方便的api这个事情本身没错 使用者自己需要使用api之前详细阅读说明

    2020-03-31
    2
    6
  • 程序员小跃
    看大家的思考题也是一种享受。 线程池这个,记得刚学会编程的时候,面试就问线程池的创建,然后回来看书知道了如何创建各种各样的线程池;但是项目中还是没遇到更多的坑,看了老师的解析,让我以后对线程池的使用更加谨慎了。

    作者回复: 的确是享受

    2020-03-17
    5
  • Darren
    先回答第二个问题吧,第一个等天亮了,试一试 不会被回收,会OOM,即使是自定义线程池,核心线程是不会回收的,每次需要10个线程,刚好是核心线程数,因此每次请求都会创建10个核心线程数的线程池,请求次数多了后,很快就回OOM。 Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

    作者回复: 👍🏻

    2020-03-12
    3
  • Joker
    老师会装门写一篇 课后回答的解答来回答这些问题吗?

    作者回复: 不会,没必要单独汇总一篇,我的答案也不一定是标准答案,看看大家的讨论不是挺好?

    2020-03-25
    2
收起评论
显示
设置
留言
73
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部