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

 更新时间:2022年05月10日 09:13:58   作者:​ Java中文社群   ​  
本文介绍了Java创建线程池为什么一定要用ThreadPoolExecutor,手动方式使用ThreadPoolExecutor创建线程池和使用Executors执行器自动创建线程池,下文更多相关内容需要的小伙伴可以参考一下

前言:

在 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(设置此值主要是为了方便演示),如下图所示: 

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

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

内存溢出原因分析

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

 构造函数的第 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("线程池中还有任务在处理");
        }
    }
}

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

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

其他创建线程池的问题

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

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

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

如下图所示: 

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

总结

线程池的创建方式总共分为两大类:手动使用 ThreadPoolExecutor 创建线程池和自动使用 Executors 执行器创建线程池的方式。其中使用 Executors 自动创建线程的方式,因为线程个数或者任务个数不可控,可能会导致内存溢出的风险,所以在创建线程池时,建议使用 ThreadPoolExecutor 的方式来创建

到此这篇关于Java创建线程池为什么一定要用ThreadPoolExecutor的文章就介绍到这了,更多相关Java线程池创建内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • idea +junit单元测试获取不到bean注入的解决方式

    idea +junit单元测试获取不到bean注入的解决方式

    这篇文章主要介绍了idea +junit单元测试获取不到bean注入的解决方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • springboot集成nacos无法动态获取nacos配置的问题

    springboot集成nacos无法动态获取nacos配置的问题

    这篇文章主要介绍了springboot集成nacos无法动态获取nacos配置的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • 使用GSON库将Java中的map键值对应结构对象转换为JSON

    使用GSON库将Java中的map键值对应结构对象转换为JSON

    GSON是由Google开发并开源的实现Java对象与JSON之间相互转换功能的类库,这里我们来看一下使用GSON库将Java中的map键值对应结构对象转换为JSON的示例:
    2016-06-06
  • Maven 生成打包可执行jar包的方法步骤

    Maven 生成打包可执行jar包的方法步骤

    这篇文章主要介绍了Maven 生成打包可执行jar包的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 大话Java混合运算规则

    大话Java混合运算规则

    这篇文章主要介绍了大话Java混合运算规则,小编觉得挺不错的,在这里分享给大家,需要的朋友可以了解下。
    2017-10-10
  • Java后端WebSocket的Tomcat实现

    Java后端WebSocket的Tomcat实现

    这篇文章主要介绍了Java后端WebSocket的Tomcat实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • MyBatisPlus+Spring实现声明式事务的方法实现

    MyBatisPlus+Spring实现声明式事务的方法实现

    本文主要介绍了MyBatisPlus+Spring实现声明式事务的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • springboot整合druid及多数据源配置的demo

    springboot整合druid及多数据源配置的demo

    这篇文章主要介绍了springboot整合druid及多数据源配置的demo,本篇主要分两部分 ①springboot整合druid的代码配置,以及druid的监控页面演示;②对实际场景中多数据源的配置使用进行讲解,需要的朋友可以参考下
    2024-01-01
  • Spring源码阅读MethodInterceptor解析

    Spring源码阅读MethodInterceptor解析

    这篇文章主要为大家介绍了Spring源码阅读MethodInterceptor使用示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • SpringBoot 拦截器返回false显示跨域问题

    SpringBoot 拦截器返回false显示跨域问题

    这篇文章主要介绍了SpringBoot 拦截器返回false显示跨域问题,文章围绕主题展开详细的内容介绍,需要的小伙伴可以参考一下
    2022-04-04

最新评论