如何创建一个线程池,为什么不推荐使用 Executors 去创建呢?
我们在学线程的时候了解了几种创建线程的方式,比如继承 Thread 类,实现 Runnable 接口、Callable 接口等,那对于线程池的使用,也需要去创建它,在这里我们提供 2 种构造线程池的方法:
方法一: 通过 ThreadPoolExecutor 构造函数来创建(首选) 这是 JDK 中最核心的线程池工具类,在 JDK1.8 中,它提供了丰富的可设置的线程池构造参数,供我们设计不同的线程池,如下:
![](https://static001.geekbang.org/infoq/3b/3bc6049d1e0fcb653355dbf9d1063d86.png)
通过构造方法 ,可以给整个线程池设置大小、等待队列、非核心线程存活时间、创建线程的工厂类、拒绝策略等,具体参数描述可见 第六问
,它们在线程池中所对应的关系,可见下图。
![](https://static001.geekbang.org/infoq/06/0616dbd8e62c3d1ab3fbf5c8af673e5c.png)
方法二: 通过 Executor 框架的工具类 Executors 来创建(不推荐) Executors 是 java 并发工具包中的一个静态工厂类,在 JDK1.5 时被创造出来,提供了丰富的创造线程池的方法,通过它可以创建多种类型的线程池。
![](https://static001.geekbang.org/infoq/67/6793820a3b92a7d028adaa002bd4d0b0.png)
newFixedThreadPool:创建定长线程池,该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。当线程发生错误结束时,线程池会补充一个新的线程;
newCachedThreadPool:创建可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,所有线程在当前任务执行完毕后,将返回线程池进行复用,线程池的容量不限制;
newScheduledThreadPool:创建定长线程池,可执行周期性的任务;
newSingleThreadExecutor:创建单线程的线程池,只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行;
newWorkStealingPool:任务可窃取线程池,不保证执行顺序,当有空闲线程时会从其他任务队列窃取任务执行,适合任务耗时差异较大的场景。
为何很多大厂都禁止使用Executors 创建线程池呢?
如果大家跟入到 Executors 这些方法的底层实现中去看一眼的话,立马就知道原因了,像 FixedThreadPool 和 SingleThreadExecutor 这两个方法内使用的是无界的 LinkedBlockingQueue 存储任务,任务队列最大长度为 Integer.MAX_VALUE,这样可能会堆积大量的请求,从而导致 OOM。
而 CachedThreadPool 使用的是同步队列 SynchronousQueue, 允许创建的线程数量也为 Integer.MAX_VALUE ,如果任务数量过多且执行速度较慢,可能会创建大量的线程,从而导致 OOM,其他的方法所提供的均是这种无界任务队列
,在高并发场景下导致 OOM 的风险很大,故大部分的公司已经不建议采用 Executors 提供的方法创建线程池了。
文章转载自:JavaBuild
评论