首页 > 编程语言 >java线程池-2

java线程池-2

时间:2024-01-25 14:33:25浏览次数:31  
标签:java Executors 队列 创建 任务 线程 ThreadPoolExecutor

1. Executors 创建线程池的潜在问题

  • 在很多公司的编程规范中,非常明确地禁止使用Executors创建线程池
  • 为什么呢?这里从源码讲起,介绍使用Executors工厂方法创建线程池将会面临的潜在问题。

1.1 Executors 创建固定数量的线程池的潜在问题

  • 使用newFixedThreadPool工厂方法固定数量的线程池的源码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
            nThreads, // 核心线程数
            nThreads, // 最大线程数
            0L,		// 线程最大空闲(Idle)时长
            TimeUnit.MILLISECONDS,// 时间单位:毫秒
            new LinkedBlockingQueue<Runnable>() //任务的排队队列,无界队列
    );
}

newFixedThreadPool工厂方法返回一个ThreadPoolExecutor实例,该线程池实例

  • corePoolSize数量为参数nThread
  • maximumPoolSize数量也为参数nThread
  • workQueue属性的值为LinkedBlockingOueue()无界阻塞队列。

使用Executors创建的固定数量的线程池的潜在问题主要存在于其workQueue上,其值为LinkedBlockingQueue(无界阻塞队列)。如果任务提交速度持续大于任务处理速度,就会造成队列中大量的任务等待。如果队列很大,很有可能导致JVM出现OOM(OutOfMemory)异常,即内存资源耗尽。

1.2 Executors 创建单线程化线程池的潜在问题

  • 使用newSingleThreadExecutor工厂方法创建单线程化线程池的源码如下
  public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(
            1,  // 核心线程数
            1,  // 最大线程数
            0L, //线程最大空闲(Idle)时长
            TimeUnit.MILLISECONDS,  // 时间单位:毫秒
            new LinkedBlockingQueue<Runnable>() // 无界队列new LinkedBlockingQueue<Runnable>()
        ));
    }

通过调用工厂方法newSingleThreadExecutor()创建一个数量为1的固定大小线程池

使用FinalizableDelegatedExecutorService对该固定大小线程池进行包装,这一层包装的作用是防止线程池的corePoolSize被动态地修改。

    public void testNewFixedThreadPool2() {
        //创建一个固定大小线程池
        ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService;
        //设置核心线程数
        threadPoolExecutor.setCorePoolSize(8);

        //创建一个单线程化的线程池
        ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();
        //转换成普通线程池, 会抛出运行时异常 java.lang.ClassCastException
        ((ThreadPoolExecutor) singleExecutorService).setCorePoolSize(8);
    }

上述代码在运行时会抛出异常。可以知道FinalizableDelegatedExecutorService实例无法被转型为ThreadPoolExecutor类型,所以也就无法修改其corePoolSize属性,从而确保单线程化线程池在运行过程中corePoolSize不会被调整,其线程数始终唯一,做到了真正的Single。

反过来说,如果没有被FinalizableDelegatedExecutorService包装原始的ThreadPoolExecutor实例是可以动态调整corePoolSize属性的。

使用Executors创建的单线程化线程池固定大小线程池一样,其潜在问题仍然存在与其workOueue属性上,该属性的值为LinkedBlockingOueue(无界阻塞队列)。如果任务提交速度持续大于任务处理速度,就会造成队列大量阻塞。如果队列很大,很有可能导致JVM的OOM异常,甚至造成内存资源耗尽。

1.3 Executors 创建可缓存线程池的潜在问题

  • 使用newCachedThreadPool工厂方法可缓存线程池的源码如下:
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(
            0,									// 核心线程数
            Integer.MAX_VALUE,					// 最大线程数
            60L,								// 线程最大空闲(Idle)时长
            TimeUnit.SECONDS,					// 时间单位:毫秒
            new SynchronousQueue<Runnable>());  // 任务的排队队列,无界队列
    }

  1. 通过调用ThreadPoolExecutor标准构造器创建一个线程池。

    • 核心线程数为0

    • 最大线程数不设限制

  2. 理论上可缓存线程池可以拥有无数个工作线程,即线程数量几乎无限制。

  3. 可缓存线程池workOueueSynchronousQueue同步队列,这个队列入队与出队必须同时传递,正因为可缓存线程池可以无限制创建线程,不会有任务等待,所以才使用SynchronousQueue

  4. 可缓存线程池有新任务到来时,新任务会被插入到SynchronousQueue实例,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可用线程则执行,若没有可用线程,则线程池会创建一个线程来执行该任务。

  5. SynchronousQueue是一个比较特殊的阻塞队列实现类,SynchronousQueue没有容量,每一个插入操作都要等待对应的删除操作,反之每个删除操作都要等待对应的插入操作。

  6. 如果使用SynchronousQueue,提交的任务不会被真实地保存,而是将新任务交给空闲线程执行,如果没有空闲线程,就创建线程,如果线程数都已经大于最大线程数,就执行拒绝策略。使用这种队列需要将maximumPoolSize设置得非常大,从而使得新任务不会被拒绝。

  7. 使用Executors创建的可缓存线程池的潜在问题存在于其最大线程数量不设上限。

  8. 由于其maximumPoolSize的值为Integer.MAX_VALUE(非常大),可以认为是无限创建线程的,如果任务提交较多,就会造成大量的线程被启动,很有可能造成OOM异常,甚至导致CPU线程资源耗尽。

1.4 Executors 创建可调度线程池的潜在问题

  • 使用newScheduledThreadPool工厂方法可调度线程池的源码如下
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
  • ExecutorsnewScheduledThreadPool工厂方法调用了ScheduledThreadPoolExecutor实现类的构造器,而ScheduledThreadPoolExecutor继承了ThreadPoolExecutor的普通线程池类,在其构造内部进一步调用了该父类的构造器
   public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(
            corePoolSize,				// 核心线程数
            Integer.MAX_VALUE,			// 最大线程数
            0,							// 线程最大空闲(Idle)时长
            NANOSECONDS,				// 时间单位
            new DelayedWorkQueue());	// 任务的排队队列
    }

  • 创建一个ThreadPoolExecutor实例
    • corePoolSize为传递来的参数
    • maximumPoolSizeInteger.MAX_VALUE,表示线程数不设上限
    • workQueue为一个DelayedWorkOueue实例,这是一个按到期时间升序排序的阻塞队列。
  • 使用Executors创建的可调度线程池的潜在问题存在于
    • 其最大线程数量不设上限。由于其线程数量不设限制,如果到期任务太多,就会导致CPU的线程资源耗尽。
  • 可调度线程池的潜在问题首先还是无界工作队列(任务排队的队列)长度都为Integer.MAX_VALUE,可能会堆积大量的任务,从而导致OOM甚至耗尽内存资源的问题。

2. 总结

  1. FixedThreadPoolSingleThreadPool
    • 这两个工厂方法所创建的线程池,工作队列(任务排队的队列)长度都为Integer.MAX_VALUE,可能会堆积大量的任务,从而导致OOM(即耗尽内存资源)。
  2. CachedThreadPool和ScheduledThreadPool
    • 这两个工厂方法所创建的线程池允许创建的线程数量为Integer.MAX_VALUE,可能会导致创建大量的线程,从而导致OOM问题。
  3. 通过源码分析发现,最大线程数参数maximumPoolSize对可调度线程池并未起作用
  4. ScheduledThreadPool内部的线程数最多为核心线程数,关键的问题还是在于其工作队列上。该线程池的工作队列(任务排队的队列)长度都为Integer.MAX_VALUE,可能会堆积大量的任务从而导致OOM问题。
  5. Executors工厂类提供了构造线程池的便捷方法,但是对于服务器程序而言,大家应该杜绝使用这些便捷方法,而是直接使用线程池ThreadPoolExecutor构造器,从而有效避免由于使用无界队列可能导致的内存资源耗尽,或者由于对线程个数不做限制而导致的CPU资源耗尽等问题。所以,要求使用标准构造器ThreadPoolExecutor创建线程池。

标签:java,Executors,队列,创建,任务,线程,ThreadPoolExecutor
From: https://www.cnblogs.com/ccblblog/p/17987090

相关文章

  • 2024年1月Java项目开发指南4:IDEA里配置MYSQL
    提前声明:文章首发博客园(cnblogs.com/mllt)自动“搬家”(同步)到CSDN,如果博客园中文章发生修改是不会同步过去的,所以建议大家到我的博客园中查看前提条件:1.你已经设计好了数据库,并成功创建了数据库。2.你的springboot项目中已经配置好了MySQL的连接。填写好信息后点测试连......
  • JavaScript 中 eval() 函数
    JavaScript的eval()函数的作用是将一个字符串作为脚本代码进行解析和执行。它可以动态地执行字符串中的JavaScript代码,并返回执行结果。eval()函数可以用于执行任何有效的JavaScript代码,包括声明变量、定义函数、执行表达式等。eval()函数的语法如下:varformArray=$('#formRec......
  • Java 中 float 与 double 的区别
    1.float是单精度浮点数,内存分配4个字节,占32位,有效小数位6-7位double是双精度浮点数,内存分配8个字节,占64位,有效小数位15位 2.java中默认声明的小数是double类型的,如doubled=4.0如果声明:floatx=4.0则会报错,需要如下写法:floatx=4.0f或者floatx=(float)4.0其中4.0f后......
  • GDB调试之多线程死锁调试(二十四)
    调试代码如下所示:#include<thread>#include<iostream>#include<vector>#include<mutex>usingnamespacestd;mutex_mutex1;mutex_mutex2;intdata1;intdata2;intdo_work_1(){ std::cout<<"线程函数do_work_1开始"<<......
  • HTTP连接池在Java中的应用:轻松应对网络拥堵
    网络拥堵是现代生活中无法避免的问题,尤其是在我们这个“点点点”时代,网页加载速度直接影响到我们的心情。此时,我们需要一位“救世主”——HTTP连接池。今天,就让我们一起探讨一下,这位“救世主”如何在Java中大显神通。首先,我们要明白,什么是HTTP连接池?简单来说,它就像一个“连接银行”......
  • 使用Java中的OkHttp库进行HTTP通信:快速、简单且高效
    在Java的世界里,进行HTTP通信的方式多种多样。其中,OkHttp以其简单、高效和强大的功能受到了开发者的广泛欢迎。今天,我们就来深入探讨如何使用OkHttp库在Java中进行HTTP通信。首先,OkHttp是一个基于HTTP/2和SPDY的客户端,提供了现代且高效的通信方式。它不仅支持同步请求和异步请求,还提......
  • rust使用lazy_static对全局变量多线程并发读写示例
    首先需要在项目依赖Cargo.toml添加lazy_static依赖项[dependencies]lazy_static="1.4.0"示例代码如下:uselazy_static::lazy_static;usestd::sync::{RwLock,RwLockReadGuard,RwLockWriteGuard};usestd::thread;#[derive(Debug)]structSharedData{data:Vec<......
  • jmeter读取csv文件控制多线程不重复读取
    在Jmeter中设置并发为S,循环次数为N时,参数化文件可能被重复读取N次,无法保证每次读取的数据均不一样,此处介绍保证数据不重复的方法。在线程组下添加一个CSVDataSetConfig,具体配置如下图:将配置中默认:RecycleonEOF=True,StopthreadonEOF=False修改为:RecycleonEO......
  • java常用集合
    java集合,也叫作容器,主要是由两大接口派生而来:一个是Collection接口,主要用于存放单一元素;另一个是Map接口,主要用于存放键值对。对于Collection接口,下面又有三个主要的子接口:List、Set和Queue。Map主要实现类HashMap,LinkHashMap,HashTable,TreeMapList主要实现类Arra......
  • java中双指针算法(快指针与慢指针)
    双指针法:设置两个指针,分别是快指针和慢指针,分别是i和j。  设置一个变量temp用来储存第一个数据nums[0] 过程:1.nums[0]  temp  他们两个相等  那么temp不需要改变,i=i+1,j变,i=1,j=1   2.nums[i=1],temp  他们两个相等,temp不变,i=i+1,j不......