详解Java线程池参数

详解Java线程池参数详解线程池参数目前线程池的类一般使用spring的:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutorJDK的:java.util.concurrent.ThreadPoolExecutor它们的配置差不多,spring的做了一些配置参数的简化,最终调用JDK的API参考资料:https://blog.c…

大家好,又见面了,我是你们的朋友全栈君。

详解Java线程池参数

目前线程池的类一般使用

  • spring的:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
  • JDK的:java.util.concurrent.ThreadPoolExecutor

它们的配置差不多,spring的做了一些配置参数的简化,最终调用JDK的API

参考资料:https://blog.csdn.net/zhouhl_cn/article/details/7392607

相关概念

概念比喻

  • 线程池(thread pool) ===>工厂

  • 线程(thread) ===>工人,属于某个工厂,被工厂所管理

  • 任务(task) ===>待工人处理的事情,即实现Runnable或Callable的类

线程池行为比喻

比喻的例子

  1. 小王(任务)去银行(线程池)办理业务,银行刚开始营业,窗口服务员还未就位(初始线程数是0)
  2. 第二行:于是经理(线程池管理者)催促柜台人员到1号窗接待(创建线程),于是小王被安排到1号窗办理业务
  3. 第三行:接着小张来办理业务,小王还没办完呢,轮不到小张,该银行总共就2个窗口(coePoolSize是2),于是经理又催促另一个窗口服务员到2号窗接待(又创建线程),小张也开始办理业务
  4. 紧接着小李(又一个任务)也来了,前面两人还没办理完呢.银行有座位1张(队列size是1),还空着呢,于是经理安排小李到座位上等候,并告知他: 如果1、2号窗空出,小李就可以前去
  5. 很不幸,窗口满了,座位也满了,这时小赵又到了银行,经理于是安排临时工(corePoolSize外的线程)在大堂站着,手持pad设备给小赵办理业务
  6. 银行业务真忙,小周又来了,经理苦呀,窗口满了,临时工也上了,座位也满了(达到了maxPoolSize),于是只能按《超出银行最大接待能力处理办法》(拒绝策略)拒绝小周办理业务
  7. 忙了一天,进来办业务的人终于少了,临时工闲了2小时(keepAliveTime),经理见他没事做,让他下班去.
  8. 由于银行规定之《正式员工闲着处理办法》(是否清理corePoolSize线程开关),即使正式工闲着,也不得提前下班,所以1、2号窗的正式工继续待着(池内保持corePoolSize个线程),经理没有办法让他们早下班

线程池参数

说明:maxPoolSize / maximumPoolSize 的意思是,spring的线程池叫maxPoolSize,而JDK线程池叫maximumPoolSize,等价

  • corePoolSize

    在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,(除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程)
    
    默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。核心线程在allowCoreThreadTimeout被设置为true时会超时并被回收,默认情况下不会被回收
    
  • maxPoolSize / maximumPoolSize

    当线程数大于或等于corePoolSize,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会**按照一定的处理策略**处理,详见rejectedExecutionHandler配置
    
  • KeepAliveSeconds / keepAliveTime

    当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0
    
  • allowCoreThreadTimeout

    是否允许核心线程空闲退出,默认值为false
    
  • queueCapacity / workQueue

    指定缓冲队列的大小和类型(仅JDK线程池可自由指定类型,详情参考附录的源码)
    
    * queueCapacity 是spring线程池的参数,int类型,默认值Integer.MAX_VALUE
    * workQueue     是JDK线程池的参数,sping线程池对此做了简化,BlockingQueue<Runnable> 类型
    
    阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
        1) ArrayBlockingQueue
        2) LinkedBlockingQueue
        3) SynchronousQueue
        4) PriorityBlockingQueue
    
  • rejectedExecutionHandler / handler

    当线程数量达到maxPoolSize时的处理策略,有
    * ThreadPoolExecutor.AbortPolicy:    丢弃任务并抛出RejectedExecutionException异常。
    * ThreadPoolExecutor.DiscardPolicy: 也是丢弃任务,但是不抛出异常。
    * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    * ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
    
    (spring线程池和JDK线程池默认值都是AbortPolicy)
    

参数调优

参数如何设置跟系统的负载有直接的关系,假设下面的参数表示目前的系统负载:

tasks,每秒需要处理的最大任务数量

tasktime,处理第个任务所需要的时间

responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒

  • corePoolSize:

    每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即tasks*tasktime个线程数。假设系统每秒任务数为1001000,每个任务耗时0.1秒,则需要100*0.1至1000*0.1,即10100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20

  • queueCapacity:

    任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400

    队列长度设置过大,会导致任务响应时间过长,切忌以下写法:

    LinkedBlockingQueue queue = new LinkedBlockingQueue();

    这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增

  • maxPoolSize:

    当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60

  • keepAliveTime:

    线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程

  • allowCoreThreadTimeout:

    默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

    以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)

线程池使用代码示例

使用spring的项目,一般如下配置线程池,整个项目使用共同的线程池,避免各自创建线程,代码如下

// 在项目中配置线程池 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
@Configuration
public class ThreadPoolConfig { 
   
    @Bean
    public ThreadPoolTaskExecutor springThreadPool() { 
   
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(50);// 核心线程数,初始是0,任务进来则创建,任务持续进来,最大达到该值
        threadPoolTaskExecutor.setMaxPoolSize(100);// 线程池维护线程的最大数量,必须在队列满后才会继续增长最大至maxPoolSize
        threadPoolTaskExecutor.setQueueCapacity(800);// 缓存队列,当线程数达到maxPoolSize且队列已满,再进来任务会被拒绝,处理方式见RejectedExecutionHandler
        threadPoolTaskExecutor.setKeepAliveSeconds(200);// 空闲多久就清理线程
        threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);// 是否清理空闲的核心线程

		// ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认)
		// ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
		// ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
		// ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
        RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
        threadPoolTaskExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);//对拒绝task的处理策略
        return threadPoolTaskExecutor;
    }
}

附:参考的源码

下面是JDK线程池源码,允许自由指定缓冲队列的大小和类型(workQueue)

// 源码java.util.concurrent.ThreadPoolExecutor
/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */
public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue,
						  ThreadFactory threadFactory,
						  RejectedExecutionHandler handler) { 
   
	if (corePoolSize < 0 ||
		maximumPoolSize <= 0 ||
		maximumPoolSize < corePoolSize ||
		keepAliveTime < 0)
		throw new IllegalArgumentException();
	if (workQueue == null || threadFactory == null || handler == null)
		throw new NullPointerException();
	this.corePoolSize = corePoolSize;
	this.maximumPoolSize = maximumPoolSize;
	this.workQueue = workQueue;
	this.keepAliveTime = unit.toNanos(keepAliveTime);
	this.threadFactory = threadFactory;
	this.handler = handler;
}

下面是spring线程池关于使用的缓冲队列类型的源码,可以看到当传入的queueCapacity大于0时使用LinkedBlockingQueue类型,默认queueCapacity值是Integer.MAX_VALUE,即默认是该类型

/** * Create the BlockingQueue to use for the ThreadPoolExecutor. * <p>A LinkedBlockingQueue instance will be created for a positive * capacity value; a SynchronousQueue else. * @param queueCapacity the specified queue capacity * @return the BlockingQueue instance * @see java.util.concurrent.LinkedBlockingQueue * @see java.util.concurrent.SynchronousQueue */
protected BlockingQueue<Runnable> createQueue(int queueCapacity) { 
   
	if (queueCapacity > 0) { 
   
		return new LinkedBlockingQueue<Runnable>(queueCapacity);
	}
	else { 
   
		return new SynchronousQueue<Runnable>();
	}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/135033.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • memwatch

    memwatch一、简介memwatch可以跟踪程序中的内存泄漏和错误,能检测双重释放(double-free)、错误释放(erroneousfree)、没有释放的内存(unfreedmemory)、溢出(Ov

  • ideal 激活码查看【在线注册码/序列号/破解码】

    ideal 激活码查看【在线注册码/序列号/破解码】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • Vue组件封装的过程[通俗易懂]

    Vue组件封装的过程[通俗易懂]Vue组件封装的过程vue组件的定义组件(Component)是Vue.js最强大的功能之一 组件可以扩展HTML元素,封装可重用代码在较高层面上,组件是自定义元素,Vue.js的编译器为他添加特殊功能某些情况下,组件也可以表现用`js`特性进行了扩展的原生的HTML元素 所有的Vue组件同时也都是Vue实例,所以可以接受相同的选项对象(除了一些根级特有的选项),并提供相同的生命周期钩子函数vue组件的功能能够把页面抽象成多个相对独立的模块实现代码重用,提高开发效率和代码

  • C语言实现读取文件的简单代码「建议收藏」

    C语言实现读取文件的简单代码「建议收藏」一、写文件#include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>intmain(){ printf(“File:%s\n”,__FILE__); printf(“Date:%s\n”,__DATE__); printf(“…

  • 计算机电脑显示器都有多大的,计算机显示器有多大? 24或27?总是有你的食物…

    计算机电脑显示器都有多大的,计算机显示器有多大? 24或27?总是有你的食物…随着电子竞技产业的日趋成熟,显示产业的重要组成部分也已经崛起,争夺分辨率地位的斗争从未停止。尽管4K显示器具有足够的细微差别,并且4K资源和游戏套件已经跟上,但业内仍然有制造商在开发6K和8K显示器…实际上,从显示技术的发展和变化来看,盲目追求分辨率是最重要的。路太偏执。实际上,一般而言,购买计算机的过程应首先选择主机性能和所需的软件支持。在满足这些要求之后,将查看显示器尺寸的选择。[购买显示…

  • Unity3D安装_3D杀号APP

    Unity3D安装_3D杀号APPUnity3D是一个跨平台的游戏开发引擎,它能够帮助开发者快速构建游戏场景,进行游戏开发与发布。它支持将开发好的作品发布到Windows、MacOS、Android、iOS、Linux等多种平台。另外,Unity3D官方版本目前可以运行在Windows和MacOS上,当然还有可以运行在Linux上的非官方版本。

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号