写点什么

开源一夏 |为什么线程池不允许使用 Executors 去创建?

  • 2022 年 8 月 26 日
    北京
  • 本文字数:2335 字

    阅读完需:约 8 分钟

开源一夏 |为什么线程池不允许使用Executors去创建?

Executors

首先来看一下线程池类 Executors 的主要方法

Executors 的创建线程池的方法,创建出来的线程池都实现了 ExecutorService 接口。常用方法有以下几个:

//创建固定数目线程的线程池ExecutorService executor1 = Executors.newFixedThreadPool(8);//创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个//新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。ExecutorService executor2 = Executors.newCachedThreadPool();//创建一个单线程化的ExecutorExecutorService executor3 = Executors.newSingleThreadExecutor();//创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类ScheduledExecutorService executor4 = Executors.newScheduledThreadPool(8);
复制代码

这个类整体来说使用起来比较方便,但是为什么说不建议用,下面来看阿里社区 Java 开发规范中的强制约束:

为什么阿里社区的 java 开发手册强制不允许使用 Executors 来创建线程,那么用 Executors 创建线程会存在什么问题呢?

Executors 存在什么问题

针对阿里 Java 开发手册提到的 OOM 问题,先模拟一段程序测试类 ExecutorsDemoController.java

package com.ruoyi.web.controller.demo.controller;
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
/** * @Description 测试线程类Executors * @Author P001 * @Date 2022/8/26 16:48 * @Version 1.0 */public class ExecutorsDemoController { private static ExecutorService executor = Executors.newFixedThreadPool(8);
public static void main(String[] args) { for (int i = 0; i < Integer.MAX_VALUE; i++) { executor.execute(new SubThread()); } }}
class SubThread implements Runnable {
@Override public void run() { try { System.out.println(Thread.currentThread().getName()); Thread.sleep(1000000); }catch (InterruptedException e) { e.printStackTrace(); } }}
复制代码

这里测试的话需要指定 JVM 参数小一点,方便很快出效果,idea 指定运行 JVM 参数如图


通过指定 JVM 参数:-Xmx8m -Xms8m 运行以上代码,会抛出 OOM:

执行结果:

错误信息:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded	at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)	at com.ruoyi.web.controller.demo.controller.ExecutorsDemoController.main(ExecutorsDemoController.java:17)
复制代码

可以看到这里是代码第 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 创建线程,并且指定具体的参数值。

private static ExecutorService execute = new ThreadPoolExecutor(10,10,60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(8));
复制代码

根据 ThreadPoolExecutor 构造函数中参数,具体参数说明如下:

corePoolSize:线程池中核心线程数的最大值

maximumPoolSize:线程池中能拥有最多线程数

keepAliveTime:表示空闲线程的存活时间

unit:表示 keepAliveTime 的单位

workQueue:用于缓存任务的阻塞队列

此处还有一个默认参数:

handler:表示当 workQueue 已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略,默认如下:

private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
复制代码

表示:抛出 RejectedExecutionException 异常

或者也可以如下创建线程:

private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()            .setNameFormat("pool-%d").build();    private static ExecutorService executor = new ThreadPoolExecutor(10,10,60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(8),namedThreadFactory);
复制代码

通过上述方式创建线程时,不仅可以避免 OOM 的问题,还可以自定义线程名称,更加方便的出错的时候溯源。


发布于: 2022 年 08 月 26 日阅读数: 45
用户头像

让技术不再枯燥,让每一位技术人爱上技术 2022.07.22 加入

还未添加个人简介

评论

发布
暂无评论
开源一夏 |为什么线程池不允许使用Executors去创建?_开源_六月的雨在infoQ_InfoQ写作社区