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

目录
  • 先说结论
  • OOM风险演示
  • 内存溢出原因分析
  • 使用ThreadPoolExecutor来改进
  • 其他创建线程池的问题
  • 总结

前言:

在 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线程池创建内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java之ThreadPoolExecutor类详解

    ThreadPoolExecutor ThreadPoolExecutor是线程池框架的一个核心类,线程池通过线程复用机制,并对线程进行统一管理 降低系统资源消耗.通过复用已存在的线程,降低线程创建和销毁造成的消耗: 提高响应速度.当有任务到达时,无需等待新线程的创建便能立即执行: 提高线程的可管理性.线程是稀缺资源,如果无限制的创建,不仅会消耗大量系统资源,还会降低系统的稳 定性,使用线程池可以进行对线程进行统一的分配.调优和监控. 线程池的运行状态总共有5种,其值和含义分别如下: RUNNI

  • 详解Java并发包中线程池ThreadPoolExecutor

    一.线程池简介 线程池的使用主要是解决两个问题:①当执行大量异步任务的时候线程池能够提供更好的性能,在不使用线程池时候,每当需要执行异步任务的时候直接new一个线程来运行的话,线程的创建和销毁都是需要开销的.而线程池中的线程是可复用的,不需要每次执行异步任务的时候重新创建和销毁线程:②线程池提供一种资源限制和管理的手段,比如可以限制线程的个数,动态的新增线程等等. 在下面的分析中,我们可以看到,线程池使用一个Integer的原子类型变量来记录线程池状态和线程池中的线程数量,通过线程池状态来控制任

  • Java并发包线程池ThreadPoolExecutor的实现

    线程池主要解决两个问题:一是当执行大量异步任务时线程池能够提供较好的性能.在不使用线程池时,每当需要执行异步任务时直接new一个线程来运行,而线程的创建和销毁都是需要开销的.线程池里面的线程是可复用的,不需要每次执行异步任务时都重新创建和销毁线程.二是线程池提供了一种资源限制和管理手段,比如可以限制线程的个数,动态新增线程等.每个ThreadPoolExecutor也保留了一些基本的统计数据,比如当前线程池完成的任务数目等. 我们首先来看一下类图 Excecutor是一个工具类,里面提供了许多静

  • java线程池ThreadPoolExecutor的八种拒绝策略示例详解

    目录 池化设计思想 线程池触发拒绝策略的时机 JDK内置4种线程池拒绝策略 拒绝策略接口定义 AbortPolicy(中止策略) DiscardPolicy(丢弃策略) DiscardOldestPolicy(弃老策略) 第三方实现的拒绝策略 Dubbo 中的线程拒绝策略 Netty 中的线程池拒绝策略 ActiveMQ 中的线程池拒绝策略 PinPoint 中的线程池拒绝策略 谈到 Java 的线程池最熟悉的莫过于 ExecutorService 接口了,jdk1.5 新增的 java.uti

  • Java ThreadPoolExecutor 线程池的使用介绍

    Executors Executors 是一个Java中的工具类. 提供工厂方法来创建不同类型的线程池. 从上图中也可以看出, Executors的创建线程池的方法, 创建出来的线程池都实现了 ExecutorService接口. 常用方法有以下几个: newFixedThreadPool(int Threads): 创建固定数目线程的线程池, 超出的线程会在队列中等待. newCachedThreadPool(): 创建一个可缓存线程池, 如果线程池长度超过处理需要, 可灵活回收空闲线程(60

  • java 定时器线程池(ScheduledThreadPoolExecutor)的实现

    前言 定时器线程池提供了定时执行任务的能力,即可以延迟执行,可以周期性执行.但定时器线程池也还是线程池,最底层实现还是ThreadPoolExecutor,可以参考我的另外一篇文章多线程–精通ThreadPoolExecutor. 特点说明 1.构造函数 public ScheduledThreadPoolExecutor(int corePoolSize) { // 对于其他几个参数在ThreadPoolExecutor中都已经详细分析过了,所以这里,将不再展开 // 这里我们可以看到调用基类

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

    目录 先说结论 OOM风险演示 内存溢出原因分析 使用ThreadPoolExecutor来改进 其他创建线程池的问题 总结 前言: 在 Java 语言中,并发编程都是依靠线程池完成的,而线程池的创建方式又有很多,但从大的分类来说,线程池的创建总共分为两大类:手动方式使用ThreadPoolExecutor创建线程池和使用 Executors 执行器自动创建线程池. 那究竟要使用哪种方式来创建线程池呢?我们今天就来详细的聊一聊. 先说结论 在 Java 语言中,一定要使用 ThreadPoolE

  • JAVA 创建线程池的注意事项

    1.创建线程或线程池时请指定有意义的线程名称,方便出错时回溯.创建线程池的时候请使用带ThreadFactory的构造函数,并且提供自定义ThreadFactory实现或者使用第三方实现. ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); ExecutorService singleThreadPool = new ThreadPoo

  • 浅谈为什么阿里巴巴要禁用Executors创建线程池

    看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,通过源码分析禁用的原因 写在前面 首先感谢大家在盖楼的间隙阅读本篇文章,通过阅读本篇文章你将了解到: 线程池的定义 Executors创建线程池的几种方式 ThreadPoolExecutor对象 线程池执行任务逻辑和线程池参数的关系 Executors创建返回ThreadPoolExecutor对象 OOM异常测试 如何定义线程池参数 如果只想知道原因可以直接拉到

  • java中线程池最实用的创建与关闭指南

    目录 前言 线程池创建 只需要执行shutdown就可以优雅关闭 执行shutdownNow关闭的测试 总结 前言 在日常的开发工作当中,线程池往往承载着一个应用中最重要的业务逻辑,因此我们有必要更多地去关注线程池的执行情况,包括异常的处理和分析等. 线程池创建 避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池.在创建的同时,给BlockQueue指定容量就可以了. private stat

  • Java如何手动创建线程池

    目录 如何手动创建线程池 构造器 队列 饱和策略 示例 源码分析 线程池工具类 实现线程的三种方式 使用ThreadPoolExecutor编写线程池工具类 如何手动创建线程池 jdk提供了一个通过ThreadPoolExecutor创建一个线程池的类 构造器 使用给定的参数和默认的饱和策略.默认的工厂方法创建线程池 ThreadPoolExecutor(int corePoolSize,  int maximumPoolSize,  long keepAliveTime,  TimeUnit

  • Java concurrency线程池之线程池原理(一)_动力节点Java学院整理

    ThreadPoolExecutor简介 ThreadPoolExecutor是线程池类.对于线程池,可以通俗的将它理解为"存放一定数量线程的一个线程集合.线程池允许若个线程同时允许,允许同时运行的线程数量就是线程池的容量:当添加的到线程池中的线程超过它的容量时,会有一部分线程阻塞等待.线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理." ThreadPoolExecutor数据结构 ThreadPoolExecutor的数据结构如下图所示: 各个数据在Thread

  • Java concurrency线程池之线程池原理(二)_动力节点Java学院整理

    线程池示例 在分析线程池之前,先看一个简单的线程池示例. import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class ThreadPoolDemo1 { public static void main(String[] args) { // 创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newFixedThre

  • 以实例简介Java中线程池的工作特点

    什么原因使我们不得不使用线程池? 个人认为主要原因是:短时间内需要处理的任务数量很多 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销 2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存 以下是Java自带的几种线程池: 1.newFixedThreadPool  创建一个指定工作线程数量的线程池. 每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中. 2.newCachedThreadPool 创建

  • java的线程池框架及线程池的原理

    java 线程池详解 什么是线程池? 提供一组线程资源用来复用线程资源的一个池子 为什么要用线程池? 线程的资源是有限的,当处理一组业务的时候,我们需要不断的创建和销毁线程,大多数情况下,我们需要反复的进行大量的创建和销毁工作,这个动作对于服务器而言,也是很浪费的一种情况,这时候我们可以利用线程池来复用这一部分已经创建过的线程资源,避免不断的创建和销毁的动作. 线程池的原理 创建好固定数量的线程,吧线程先存下来,有任务提交的时候,把资源放到等待队列中,等待线程池中的任务队列不断的去消费处理这个队

  • Java concurrency线程池之线程池原理(四)_动力节点Java学院整理

    拒绝策略介绍 线程池的拒绝策略,是指当任务添加到线程池中被拒绝,而采取的处理措施. 当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭.第二,任务数量超过线程池的最大限制. 线程池共包括4种拒绝策略,它们分别是:AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy和DiscardPolicy. AbortPolicy         -- 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException

随机推荐