网站首页 > 技术文章 正文
线程作为一种计算机珍贵的资源,在实际的开发中,我们通常会采用池化技术(线程池)来缓存线程对象。
下面我们通过三个问题来看看,使用线程池应该注意的问题。
线程池应该手动声明
在java的JDK中提供了Executors类可以快速地创建一些线程池,具体代码如下
// 创建工作线程数固定的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
// 创建一个线程可复用的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
// 创建只有一个工作线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
// 创建定时/延时工作线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
在《阿里巴巴java开发手册》中有着明确的强制性的要求:【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。最典型的就是FixedThreadPool和CachedThreadPool,可能会因为资源耗尽,进而导致OOM。
- FixedThreadPool是如何造成OOM
我们先编写一段测试代码,代码大致要执行的逻辑是:初始化一个工作线程数为2的FixedThreadPool,循环1亿次向线程池提交任务,每个线程内执行的逻辑是,创建一个比较大的字符串,然后再休眠30min。执行该代码一段时间后,可以看到打印出来如下的错误信息。
public static void oom1() throws InterruptedException {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
//打印线程池的信息,稍后我会解释这段代码
for (int i = 0; i < 100000000; i++) {
threadPool.execute(() -> {
String payload = IntStream.range(1, 1000000)
.mapToObj(k -> "a")
.collect(Collectors.joining("")) + UUID.randomUUID().toString();
try {
TimeUnit.MINUTES.sleep(30);
} catch (InterruptedException e) {
}
log.info(payload);
});
}
threadPool.shutdown();
threadPool.awaitTermination(1, TimeUnit.HOURS);
}
PS:如果大家不好模拟出来OMM,可以设置jvm的最大堆。
问题剖析:打开newFixedThreadPool的源码,我们不难发现问题的根源“线程池的任务队列创建了一个LinkedBlockingQueue,而LinkedBlockingQueue的默认构造方法是创建一个长度为Integer.MAX_VALUE的队列,实际使用中可以认为该队列是无界队列,虽然线程池的工作线程数是固定的,但如果快速提交了大量任务且这些任务的执行都比较耗时,未执行的任务会存储在队列中,以致撑爆内存导致OOM”
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
- CachedThreadPool是如何造成OOM
我们稍微修改一下上面的代码,将newFixedThreadPool改为newCachedThreadPool,代码如下
public static void oom2() throws InterruptedException {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
//打印线程池的信息,稍后我会解释这段代码
for (int i = 0; i < 100000000; i++) {
threadPool.execute(() -> {
String payload = IntStream.range(1, 1000000)
.mapToObj(k -> "a")
.collect(Collectors.joining("")) + UUID.randomUUID().toString();
try {
TimeUnit.MINUTES.sleep(30);
} catch (InterruptedException e) {
}
log.info(payload);
});
}
threadPool.shutdown();
threadPool.awaitTermination(1, TimeUnit.HOURS);
}
程序运行后,很快就打印出了如上的错误。
问题剖析:打开newCachedThreadPool的源码,我们不难发现问题的根源“线程池的工作线程数是Integer.MAX_VALUE,而其任务队列SynchronousQueue是一个没有存储空间的阻塞队列,因此实际使用中当接收到一个任务后,就必须有一个线程来处理该任务,如果当前的线程池没有空闲的线程,就会创建一个新的线程”,此时由于我们的任务执行比较耗时,提交大量任务就需要创建大量得线程,我们都知道创建线程是需要分配一定得内存空间来作为线程栈得,例如1MB。如果无限制的创建线程,必然会导致OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public SynchronousQueue() {
this(false);
}
因此,在实际的开发中我们是强制不要使用以上方法创建线程池。除了因为会造成OOM的原因外还有以下两点:
- 通常情况下,我们需要根据自己的业务场景、并发情况以及服务器的硬件配置等因素来综合评估线程池的核心参数,包含核心线程数、最大线程数、任务队列类型、线程回收策略以及任务拒绝策略,确保我们创建的线程池符合需求,一般都需要设置有界的任务队列和可控的工作线程数。
- 任何场景下,都应该为自定义的线程池指定有意义的名称,方便出现问题时,进行排查。
猜你喜欢
- 2024-10-22 线程池调优之动态参数配置 动态设置线程池大小
- 2024-10-22 java线程池参数及使用 java线程池的用法
- 2024-10-22 面试官:说说你对线程池的了解 线程池实现原理面试
- 2024-10-22 「每日分享」高阶程序员需要掌握的常见性能优化策略
- 2024-10-22 池化技术学习 池化方法
- 2024-10-22 「Java基础」「多线程」-线程池 java多个线程池
- 2024-10-22 线程池配置的常见误区 线程池配置参数有哪些
- 2024-10-22 创建线程池参数有哪些作用? 创建线程池的7个参数
- 2024-10-22 一文搞懂!多线程之间的通信及线程池
- 2024-10-22 码农大叔带你——解析线程池 线程池的主要处理流程
你 发表评论:
欢迎- 最近发表
-
- 吴谨言专访大反转!痛批耍大牌后竟翻红,六公主七连发力显真诚
- 港股2月28日物业股涨幅榜:CHINAOVSPPT涨1.72%位居首位
- 港股2月28日物业股午盘:CHINAOVSPPT涨1.72%位居首位
- 港股3月2日物业股涨幅榜:CHINAOVSPPT涨1.03%位居首位
- 港股3月2日物业股午盘:CHINAOVSPPT涨1.03%
- 天赋与心痛的背后:邓鸣贺成长悲剧引发的深刻反思
- 冯小刚女儿徐朵追星范丞丞 同框合照曝光惹人羡,回应网友尽显亲民
- “资本大佬”王冉:51岁娶小17岁童瑶,并承诺余生为娇妻保驾护航
- 港股3月2日物业股午盘:CHINAOVSPPT涨1.03%位居首位
- 「IT之家开箱」vivo S15 图赏:双镜云窗,盛夏风光
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)