开源一夏 |为什么线程池不允许使用 Executors 去创建?
Executors
首先来看一下线程池类 Executors 的主要方法
Executors 的创建线程池的方法,创建出来的线程池都实现了 ExecutorService 接口。常用方法有以下几个:
这个类整体来说使用起来比较方便,但是为什么说不建议用,下面来看阿里社区 Java 开发规范中的强制约束:
为什么阿里社区的 java 开发手册强制不允许使用 Executors 来创建线程,那么用 Executors 创建线程会存在什么问题呢?
Executors 存在什么问题
针对阿里 Java 开发手册提到的 OOM 问题,先模拟一段程序测试类 ExecutorsDemoController.java
这里测试的话需要指定 JVM 参数小一点,方便很快出效果,idea 指定运行 JVM 参数如图
通过指定 JVM 参数:-Xmx8m -Xms8m 运行以上代码,会抛出 OOM:
执行结果:
错误信息:
可以看到这里是代码第 17 行发生异常
下面再来说一下为什么会发生这样的情况
Executors 为什么会 OOM
通过上面的报错信息可以看出,其中真正导致 OOM 的是 LinkedBlockingQueue.offer(E e),查看 Executors.newFixedThreadPool(int nThreads)的源码,看到阻塞队列用的是 new LinkedBlockingQueue<Runnable>();
Java 中的 BlockingQueue 主要有两种实现,分别是 ArrayBlockingQueue 和 LinkedBlockingQueue。
ArrayBlockingQueue 是一个用数组实现的有界阻塞队列,必须设置容量。如图
LinkedBlockingQueue 是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为 Integer.MAX_VALUE。如图
这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为 Integer.MAX_VALUE。也就是说,如果我们不设置 LinkedBlockingQueue 的容量的话,其默认容量将会是 Integer.MAX_VALUE。
而 newFixedThreadPool 中创建 LinkedBlockingQueue 时,并未指定容量。此时,LinkedBlockingQueue 就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。
上面提到的问题除了我们测试用的 newFixedThreadPool,还有 newSingleThreadExecutor,但是并不是说 newCachedThreadPool 和 newScheduledThreadPool 就不会出问题,他们两个创建的最大线程数可能是 Integer.MAX_VALUE,而创建巨多的线程也有可能导致 OOM。
创建线程池的正确姿势
避免使用 Executors 创建线程主要是避免其中一些参数给的默认值,那么可以直接用 ThreadPoolExecutor 创建线程,并且指定具体的参数值。
根据 ThreadPoolExecutor 构造函数中参数,具体参数说明如下:
corePoolSize:线程池中核心线程数的最大值
maximumPoolSize:线程池中能拥有最多线程数
keepAliveTime:表示空闲线程的存活时间
unit:表示 keepAliveTime 的单位
workQueue:用于缓存任务的阻塞队列
此处还有一个默认参数:
handler:表示当 workQueue 已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略,默认如下:
表示:抛出 RejectedExecutionException 异常
或者也可以如下创建线程:
通过上述方式创建线程时,不仅可以避免 OOM 的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
版权声明: 本文为 InfoQ 作者【六月的雨在infoQ】的原创文章。
原文链接:【http://xie.infoq.cn/article/9c09b7127b7a12326b01f07be】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论