Java创建线程池为什么一定要用ThreadPoolExecutor

作者:??Java中文社群???? 时间:2023-04-22 06:03:31 

前言:

在 Java 语言中,并发编程都是依靠线程池完成的,而线程池的创建方式又有很多,但从大的分类来说,线程池的创建总共分为两大类:手动方式使用ThreadPoolExecutor创建线程池和使用 Executors 执行器自动创建线程池。 那究竟要使用哪种方式来创建线程池呢?我们今天就来详细的聊一聊。

先说结论

在 Java 语言中,一定要使用 ThreadPoolExecutor 手动的方式来创建线程池,因为这种方式可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控,并且可以规避资源耗尽的风险。

OOM风险演示

假如我们使用了 Executors 执行器自动创建线程池的方式来创建线程池,那么就会存现线程溢出的风险,

以 CachedThreadPool 为例我们来演示一下:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExecutorExample {
   static class OOMClass {
       // 创建 1MB 大小的变量(1M = 1024KB = 1024*1024Byte)
       private byte[] data_byte = new byte[1 * 1024 * 1024];
   }
   public static void main(String[] args) throws InterruptedException {
       // 使用执行器自动创建线程池
       ExecutorService threadPool = Executors.newCachedThreadPool();
       List<Object> list = new ArrayList<>();
       // 添加任务
       for (int i = 0; i < 10; i++) {
           int finalI = i;
           threadPool.execute(new Runnable() {
               @Override
               public void run() {
                   // 定时添加
                   try {
                       Thread.sleep(finalI * 200);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   // 将 1M 对象添加到集合
                   OOMClass oomClass = new OOMClass();
                   list.add(oomClass);
                   System.out.println("执行任务:" + finalI);
               }
           });
       }
   }
}

第 2 步将 Idea 中 JVM 最大运行内存设置为 10M(设置此值主要是为了方便演示),如下图所示: 

Java创建线程池为什么一定要用ThreadPoolExecutor

 以上程序的执行结果如下图所示: 

Java创建线程池为什么一定要用ThreadPoolExecutor

 从上述结果可以看出,当线程执行了 7 次之后就开始出现OutOfMemoryError内存溢出的异常了。

内存溢出原因分析

想要了解内存溢出的原因,我们需要查看 CachedThreadPool 实现的细节,它的源码如下图所示:

Java创建线程池为什么一定要用ThreadPoolExecutor

 构造函数的第 2 个参数被设置成了 Integer.MAX_VALUE,这个参数的含义是最大线程数,所以由于 CachedThreadPool 并不限制线程的数量,当任务数量特别多的时候,就会创建非常多的线程。而上面的 OOM 示例,每个线程至少要消耗 1M 大小的内存,加上 JDK 系统类的加载也要占用一部分的内存,所以当总的运行内存大于 10M 的时候,就出现内存溢出的问题了。

使用ThreadPoolExecutor来改进

接下来我们使用 ThreadPoolExecutor 来改进一下 OOM 的问题,我们使用 ThreadPoolExecutor 手动创建线程池的方式,创建一个最大线程数为 2,最多可存储 2 个任务的线程池,并且设置线程池的拒绝策略为忽略新任务,这样就能保证线程池的运行内存大小不会超过 10M 了,

实现代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* ThreadPoolExecutor 演示示例
*/
public class ThreadPoolExecutorExample {
   static class OOMClass {
       // 创建 1MB 大小的变量(1M = 1024KB = 1024*1024Byte)
       private byte[] data_byte = new byte[1 * 1024 * 1024];
   }

public static void main(String[] args) throws InterruptedException {
       // 手动创建线程池,最大线程数 2,最多存储 2 个任务,其他任务会被忽略
       ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2,
               0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),
               new ThreadPoolExecutor.DiscardPolicy()); // 拒绝策略:忽略任务
       List<Object> list = new ArrayList<>();
       // 添加任务
       for (int i = 0; i < 10; i++) {
           int finalI = i;
           threadPool.execute(new Runnable() {
               @Override
               public void run() {
                   // 定时添加
                   try {
                       Thread.sleep(finalI * 200);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   // 将 1m 对象添加到集合
                   OOMClass oomClass = new OOMClass();
                   list.add(oomClass);
                   System.out.println("执行任务:" + finalI);
               }
           });
       }
       // 关闭线程池
       threadPool.shutdown();
       // 检测线程池的任务执行完
       while (!threadPool.awaitTermination(3, TimeUnit.SECONDS)) {
           System.out.println("线程池中还有任务在处理");
       }
   }
}

以上程序的执行结果如下图所示: 

Java创建线程池为什么一定要用ThreadPoolExecutor

 从上述结果可以看出,线程池从开始执行到执行结束都没有出现 OOM 的异常,这就是手动创建线程池的优势。

其他创建线程池的问题

除了 CachedThreadPool 线程池之外,其他使用 Executors 自动创建线程池的方式,也存在着其他一些问题,

比如 FixedThreadPool 它的实现源码如下: 

Java创建线程池为什么一定要用ThreadPoolExecutor

而默认情况下任务队列LinkedBlockingQueue的存储容量是 Integer.MAX_VALUE,也是趋向于无限大

如下图所示: 

Java创建线程池为什么一定要用ThreadPoolExecutor

 这样就也会造成,因为线程池的任务过多而导致的内存溢出问题。其他几个使用 Executors 自动创建线程池的方式也存在此问题,这里就不一一演示了。

来源:https://juejin.cn/post/7077358563764469796

标签:Java,ThreadPoolExecutor,创建,线程池
0
投稿

猜你喜欢

  • 教你怎么用java一键自动生成数据库文档

    2021-08-01 02:34:36
  • java实现List中对象排序的方法

    2023-11-08 21:36:29
  • Android TextView中文字通过SpannableString设置属性用法示例

    2023-07-26 07:11:51
  • Java封装公共Result结果返回类的实现

    2023-06-17 08:47:47
  • 微信公众号服务号推送模板消息设置方法(后端java)

    2023-11-20 08:27:58
  • Java设计模式之单例和原型

    2023-11-29 04:14:18
  • IDEA实现添加 前进后退 到工具栏的操作

    2021-08-30 21:34:48
  • springboot 使用QQ邮箱发送邮件的操作方法

    2022-03-03 14:36:22
  • Java开发中synchronized的定义及用法详解

    2021-11-11 07:01:20
  • Java实现新建有返回值的线程的示例详解

    2022-02-13 17:09:33
  • 详解如何在SpringBoot中自定义参数解析器

    2023-07-24 16:06:51
  • Spring Cloud 的 Hystrix.功能及实践详解

    2023-11-19 06:40:46
  • JAVA提高第八篇 动态代理技术

    2023-07-19 07:13:12
  • Spring Boot项目添加外部Jar包以及配置多数据源的完整步骤

    2023-10-12 13:20:39
  • 深入理解Java中观察者模式与委托的对比

    2023-10-30 18:00:03
  • 使用Spring组合自定义的注释 mscharhag操作

    2023-02-14 11:18:01
  • Java 替换word文档文字并指定位置插入图片

    2023-08-12 22:02:25
  • android6.0权限动态申请框架permissiondispatcher的方法

    2023-07-31 10:51:57
  • 腾讯云部署javaWeb项目的实现步骤

    2023-11-05 18:03:29
  • java如何导出insert语句并生成sql脚本

    2022-05-18 10:33:54
  • asp之家 软件编程 m.aspxhome.com