又又又踩坑了
生产有个对账系统,每天需要从渠道端下载对账文件,然后开始日终对账。这个系统已经运行了很久,前两天突然收到短信预警,没有获取渠道端对账文件。
ps:对账系统详细实现方式:对账系统设计与实现
本以为又是渠道端搞事情,上去一排查才发现,所有下载任务都被阻塞了。再进一步排查源码,才发现自己一直用错了线程池某个方法。
由于线程创建比较昂贵,正式项目中我们都会使用线程池执行异步任务。线程池,使用池化技术保存线程对象,使用的时候直接取出来,用完归还以便使用。
虽然线程池的使用非常方法非常简单,但是越简单,越容易踩坑。细数一下,这些年来因为线程池导致生产事故也有好几起。
所以今天,小黑哥就针对线程池的话题,给大家演示一下怎么使用线程池才会踩坑。
希望大家看完,可以完美避开这些坑~
先赞后看,养成习惯。微信搜索「程序通事」,关注就完事了!
慎用 Executors 组件
Java 从 JDK1.5 开始提供线程池的实现类,我们只需要在构造函数内传入相关参数,就可以创建一个线程池。
不过线程池的构造函数可以说非常复杂,就算最简单的那个构造函数,也需要传入 5 个参数。这对于新手来说,非常不方便哇。
也许 JDK 开发者也考虑到这个问题,所以非常贴心给我们提供一个工具类 Executors
,用来快捷创建创建线程池。
虽然这个工具类使用真的非常方便,可以少写很多代码,但是小黑哥还是建议生产系统还是老老实实手动创建线程池,慎用Executors
,尤其是工具类中两个方法 Executors#newFixedThreadPool
与 Executors#newCachedThreadPool
。
如果你图了方便使用上述方法创建了线程池,那就是一颗定时炸弹,说不准那一天生产系统就会💥。
我们来看两个🌰,看下这个这两个方法会有什么问题。
假设我们有个应用有个批量接口,每次请求将会下载 100w 个文件,这里我们使用 Executors#newFixedThreadPool
批量下载。
下面方法中,我们随机休眠,模拟真实下载耗时。
为了快速复现问题,调整 JVM 参数为
-Xmx128m -Xms128m
。
private ExecutorService threadPool = Executors.newFixedThreadPool(10);
/**
* 批量下载对账文件
*
* @return
*/
@RequestMapping("/batchDownload")
public String batchDownload() {
// 模拟下载 100w 个文件
for (int i = 0; i < 1000000; i++) {
threadPool.execute(() -> {
// 随机休眠,模拟下载耗时
Random random = new Random();
try {
TimeUnit.SECONDS.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
return "process";
}
程序运行之后,多请求几次这个批量下载方法,程序很快就会 OOM 。
查看 Executors#newFixedThreadPool