java线程池参数自定义设置详解

 更新时间:2023年11月15日 10:11:54   作者:zhongh Jim  
这篇文章主要为大家介绍了java线程池参数自定义设置详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

上一篇线程池+ FutureTask异步执行多任务只介绍了怎么搭配使用线程池,但没有说明里面的线程池的参数是怎么设置的,那么本文就说明一下。

这里把上篇文章的线程池参数设置贴出来:

//给这个接口的线程池定义里边的线程名字
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-start-runner-%d").build();
ExecutorService taskExe= new ThreadPoolExecutor(10,20,800L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(100),namedThreadFactory);

这些参数也不都是随意设置的,而是有一定的考量思路,下面会一 一介绍

先介绍一下线程池的构造函数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    ...
}

我们创建线程池一般是手动设置线程池的参数,已经不建议使用Executors的FixedThreadPool 、SingleThreadPool、CachedThreadPool了

因为:

  • FixedThreadPool 、SingleThreadPool会的任务等待队列均为new LinkedBlockingQueue<Runnable>(),允许的队列长度为 Integer.MAX_VALUE,存在任务堆积导致OOM内存溢出的隐患
  • 而CachedThreadPool允许的最大线程数量为Integer.MAX_VALUE,而且核心线程数为0,意味着 只要有任务进来,就会频繁创建新线程,没有任务之后又要关闭线程,耗费性能。另一方面,由于允许创建大量的线程,也有导致OOM的潜在隐患

设置线程池参数需要参考几个数值:

tasks:每秒任务数,运维反馈是平均每秒 38个

taskcost:每个任务花费时间,0.2s

(3) responsetime:系统容忍(线程等待最长时间)的最大时间1s

corePoolSize:核心线程数

核心线程会一直存活,不管空不空闲,但如果设置了setAllowCoreThreadTimeout(true)会让核心线程在空闲超时后关闭

计算方式:corePoolSize=tasks/(1/taskcost) =tasks * taskcost =38*0.2=7.6 个

查阅了下文章,大佬说计算密集型(遍历+判断的逻辑耗时占比多)的接口可将核心线程设置为:

corePoolSize=CPU核数+1 =8+1=9,设置为10就好了

如何查看CPU核数:

System.out.println(Runtime.getRuntime().availableProcessors());

设置得稍微大一点,也能减少频繁创建额外线程带来的开销

maxPoolSize:最大线程数

如果核心线程数不够用,会创建额外的线程来执行任务。

创建额外线程的条件(缺一不可):

  • 现有的线程数< 最大线程数maxPoolSize and 现有线程数 > corePoolSize核心线程数
  • 任务队列填满了

最大线程数我们设置的相对随意了些, 令maxPoolSize= 2* corePoolSize=20,大概能应对突然暴增的业务查询请求

keepAliveTime额外线程的可空闲时间

额外线程就是在核心线程数的基础上 另外创建的线程

额外线程空闲了keepAliveTime的时间后,线程退出,直至现有的线程数量=corePoolSize核心线程数

  • TimeUnit.MILLISECONDS是毫秒单位

workQueue任务队列

常见的有3种:

(1) 无限队列LinkedBlockingQueue()

构造函数是new LinkedBlockingQueue<Runnable>()

允许的任务等待队列的最大长度为:Integer.MAX_VALUE,即能无限的接收新的任务,任何的拒绝策略也差不多没有意义了

另外,maximumPoolSize这个参数也没有意义了,因为只有同时满足 核心线程数量够了 + 任务队列workQueue满了 + 现有的线程数<maximumPoolSize最大线程数,才会去创建额外的线程

  • 好处是LinkedBlockingQueue在应对突然暴增的请求时,它不会抛异常拒绝
  • 缺点是任务堆积过度没有及时处理的话,容易导致内存溢出

那咱们就不用这个队列了吧

(2) 有界队列

  • new LinkedBlockingQueue(int capacity):固定容量的阻塞队列
  • new ArrayBlockingQueue<Integer>(int capacity,true);其中true是公平锁,只能FIFO排队一 一执行;false允许任务插队,会存在晚来的任务先执行的情况
  • PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator):默认会创建长度为11的优先级队列,第二个参数comparator会按照我们指定的方式进行排序

我们的任务基本的执行顺序基本也是先进先出,直接用了new LinkedBlockingQueue(int capacity),把容量设置得大一点,那样就不会轻易的填满队列导致频繁地创建额外的线程,减少线程频繁切换

(3) SynchronousQueue

new SynchronousQueue():队列长度为0,要添加新任务必须得有空闲的线程才能添加,因此要求 maximumPoolSize尽可能的大,还得 配置拒绝策略

最终, 我们选择了new LinkedBlockingQueue(int capacity)作为任务队列

任务队列的长度

queueCapacity = (coreSize/taskcost) * responsetime=8/0.2*1=80,队列长度设置为100也可

RejectedExecutionHandler拒绝策略

  • AbortPolicy:抛异常
  • DiscardPolicy:丢任务
  • DiscardOldestPolicy:将队列头部的任务丢了,也就是把最早进入队列等待的任务丢了
  • CallerRunsPolicy:将新任务(皮球)踢回给主线程执行,让主线程在接下来的时间能无法提交新任务,典型的踢皮球策略

我们选择了默认的AbortPolicy抛异常:

抛异常的话,需要上游系统截获异常,并告知用户请求繁忙稍等一下

如果是DiscardPolicy丢任务的话我猜大概率是用户得不到响应吧,没这么搞过

线程工厂

它还是很有必要设置的,因为系统的线程池不止一个,不设置一下线程工厂,不给线程定义个名字的话,很难看到是哪个线程池的线程在跑,因为线程的名字都被写死成pool-1-thread-1pool-1-thread-2pool-2-thread-1

那么,问题来了:如何判断线程池里边的指定线程是否在执行任务?

更多关于线程池参数自定义的资料请关注脚本之家其它相关文章!

相关文章

最新评论