文章目录

  • 1 线程池快速回顾
  • 2 现有设置参数的方法及不足
  • 3 如何设置核心线程数(corePoolSize)
  • 4 如何设置最大线程数(maxPoolSize)
  • 5 如何改变等待队列长度

想必大家对Java里面线程池( 类)一定不陌生吧,无论是在日常工作还是面试题里都经常会有它的身影,特别是在当前CPU动辄就是好多核的背景下,了解并使用线程池已经成为一名合格后端开发的基本功了。

相信大家也一定思考过一个问题,面对各种各样的场景,线程池的参数到底应该怎么设计呢?这一定是一个超级难以回答的问题,几天前的我也想不到一个标准的答案,好在是发现了美团在2020年发表过的一篇文章,里面给了一个非常高级的操作——让线程池的参数动态化,这就极大地提高了系统的自适应能力。

https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

至于为什么我现在才看到=_=,可能因为是太懒了吧。。。好在及时发现,在此基础上进行一些分析,不理解线程池的小伙伴们也不用担心,我们首先来回顾一下它的核心思想,在此技术上介绍如何将参数动态化起来~


1 线程池快速回顾

《Java 并发编程的艺术》中提到了使用线程池的好处,概括起来如下:

  • 降低资源损耗。通过重复利用已创建的线程降低线程创建和销毁的损耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。使用线程池可以进行统一的分配,调优和监控。

Java里使用线程池,主要就是用的ThreadPoolExecutor类,先来看一下 ThreadPoolExecutor 类中的构造方法:

/*** 用给定的初始参数创建一个新的ThreadPoolExecutor。*/
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;
}

ThreadPoolExecutor 中最重要的参数:

  • corePoolSize:核心线程数。最小可以同时运行的线程数。
  • maximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的最大线程数。
  • workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到corePoolSize,如果达到的话,新任务就会被存放在队列中。如果workQueue已经满了的话就执行拒绝策略。

ThreadPoolExecutor 的其他参数:

  • keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime 才会被销毁。
  • unit : keepAliveTime 参数的时间单位。
  • threadFactoryexecutor 创建新线程的时候会用到。
  • handler:拒绝策略。

当参数设置完毕后,线程池的工作原理具体是什么呢?我们可以通过下面这个面试题来理解一下:

假设我们设置的线程池参数为:corePoolSize=10, maximumPoolSize=20,queueSize = 10
20个并发任务过来,有多少个活跃线程?

10个。corePoolSize打满,queueSize 也满

21个并发任务过来,有多少个活跃线程?

11个。corePoolSize打满,queueSize 也满还多一个,maximumPoolSize = 20,所以corePoolSize + 1此时活跃的为11个。

30个并发任务过来,有多少个活跃线程?

20个。corePoolSize打满,queueSize 也满,corePoolSize扩充至20,此时有20个活跃任务。

31个并发任务过来,有多少个活跃线程?

20个。corePoolSize打满,queueSize 也满,corePoolSize扩充至20还多一个,如果是丢弃策略,此时有20个活跃任务。


上面的流程可以总结成如下所示的流程图:(来源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)

2 现有设置参数的方法及不足

回顾完线程池的核心技术点之后就要开始思考本文主要讨论的内容了:线程池参数应该如何设置?

如果你把这个问题输入到浏览器里,极大可能是下面这种答案:

上面的理论看似很华丽,但现实却是很残酷的。。。你会发现虽然按照上面的指导思想进行配置了,但效果并不能让人满意,造成这种后果的原因有很多,包括但不仅限于:

  1. 任务到底是CPU还是IO密集的特征不明显
  2. 同一个机器上可能部署不止一个服务,不同服务之间也会抢占资源

针对上述问题,美团给出的对应的解决方案就是——线程池参数动态化

那么如何实现参数动态化呢?

接触过微服务开发的同学们可能就会想到了,我们完全可以借助一个配置中心来做,这样就能够实现线程池参数的动态配置和即时生效(在阿里内部也有一个专门的中间件,diamond),省去了重新部署程序并发布的步骤,通常在企业里这一系列流程下来还是比较费时间的。


(来源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)

3 如何设置核心线程数(corePoolSize)

其实 ThreadPoolExecutor 类库里直接就有这个方法:

public void setCorePoolSize(int corePoolSize) {if (corePoolSize < 0)throw new IllegalArgumentException();int delta = corePoolSize - this.corePoolSize;this.corePoolSize = corePoolSize;if (workerCountOf(ctl.get()) > corePoolSize)interruptIdleWorkers();else if (delta > 0) {// We don't really know how many new threads are "needed".// As a heuristic, prestart enough new workers (up to new// core size) to handle the current number of tasks in// queue, but stop if queue becomes empty while doing so.int k = Math.min(delta, workQueue.size());while (k-- > 0 && addWorker(null, true)) {if (workQueue.isEmpty())break;}}
}


我们直接看英文注释,这就是作者直接想要表达的意思。大致翻译一下:

设置线程的核心数量,如果新的corePoolSize值小于当前corePoolSize值,多出来的线程将在其下次空闲时被终止。如果新的corePoolSize值大于当前corePoolSize值,就可以创建新的worker来执行队列里的任务


(来源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)

4 如何设置最大线程数(maxPoolSize)

同样地 ThreadPoolExecutor 类库里也有这个方法:

public void setMaximumPoolSize(int maximumPoolSize) {if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)throw new IllegalArgumentException();this.maximumPoolSize = maximumPoolSize;if (workerCountOf(ctl.get()) > maximumPoolSize)interruptIdleWorkers();
}

这个方法的注释和上面的方法类似,大家可以对照着看:

逻辑也并不复杂:

  1. 参数校验
  2. 设置最大线程数 maxPoolSize
  3. 如果工作线程数是否大于最大线程数,则对空闲线程发起中断

JDK原生线程池ThreadPoolExecutor还提供了其他设置参数的方法:

5 如何改变等待队列长度

等待队列的长度capacityfinal修饰符修饰,所以按理说是不能修改的

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

唯一可能的办法就是自己定义一个队列,在美团的实现里就是一个名为ResizableCapacityLinkedBlockIngQueue的队列,根据名称也不难看出,这个队列的容量是可变的。

具体的实现细节美团好像并没有公布出来,不过我们可以简单的将原先LinkedBlockingQueuecapacityfinal修饰符去掉,并提供getter和setter方法,形成我们自己的ResizableCapacityLinkedBlockIngQueue

线程池参数到底要怎么配?相关推荐

  1. 线程池参数到底要怎么配?这可能是最好的答案

  2. 【并发编程】线程池参数设置与动态调整

    看了美团的一篇技术文章后才知道原来线程池的参数还可以动态调节. 一.场景分析 1.1 一个线程池中的线程异常了,那么线程池会怎么处理这个线程? public class ThreadPoolExecu ...

  3. 生产上如何设置线程池参数?拒绝策略怎么配?|| Executors 中 JDK 给你提供了,为什么不用??

    生产上如何设置线程池参数?拒绝策略怎么配?

  4. 自定义线程池-参数设计分析

    自定义线程池-参数设计分析 通过观察Java中的内置线程池参数讲解和线程池工作流程总结,我们不难发现,要设计一个好的线程池,就必须合理的设置线程池的4个参数;那到底该如何合理的设计4个参数的值呢?我们 ...

  5. 线程池参数如何设置?

    前言 着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发 ...

  6. 动态调整线程池参数实践

    欢迎大家关注我的微信公众号[老周聊架构],Java后端主流技术栈的原理.源码分析.架构以及各种互联网高并发.高性能.高可用的解决方案. 一.线程池遇到的挑战 我们上一篇 <一文读懂线程池的实现原 ...

  7. 线程池传递对象参数_一次线程池参数错误引起的线上故障

    在JAVA里,我们通常会把没有前后依赖关系的逻辑操作扔到多个线程里并行执行,以提高代码运行效率. 同时,我们一般也不会单独显式创建线程,而是通过线程池设置线程.使用线程池的好处是减少在创建和销毁线程上 ...

  8. 【Java 并发编程】线程池机制 ( 线程池阻塞队列 | 线程池拒绝策略 | 使用 ThreadPoolExecutor 自定义线程池参数 )

    文章目录 一.线程池阻塞队列 二.拒绝策略 三.使用 ThreadPoolExecutor 自定义线程池参数 一.线程池阻塞队列 线程池阻塞队列是线程池创建的第 555 个参数 : BlockingQ ...

  9. 线程池参数详解_java中常见的六种线程池详解

    之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java 中常见的几种线程池,以及在jdk7 加入的 ForkJoin 新型线程池 首先我们列出Java 中的六种线程池如 ...

最新文章

  1. python代码统计字符串中大写字符、小写字符、特殊字符以及数值字符出现的次数
  2. FPGA等效门数的计算方法
  3. 微服务+:服务契约治理
  4. OGRE手册--脚本texture_unit
  5. 机器学习-数据科学库(第一天)
  6. [链接]开方检验原理
  7. 昆明第八中学2021高考成绩查询,昆明市第八中学2021年招生录取分数线
  8. Caffe Batch Normalization推导
  9. saltstack系列2之zabbix-agent自动化部署
  10. mysql urlencode 中文_php url中文转码的方法
  11. 恒生O32系统的前世今生
  12. Zeta电位计算理论
  13. 手把手教你用Python创建SQL数据库~
  14. Kafka-Failover笔记
  15. javac不是内部或外部命令,也不是可运行的程序 或批处理文件的细节问题(window10)
  16. GRECP/LPL RECOVERY
  17. 吴恩达AI FOR Everyone|人工智能入门笔记|
  18. Kali使用apt-cache search搜索想要的软件包
  19. 2018腾讯游戏面试总结
  20. 安卓Launcher 简介

热门文章

  1. 指针在c语言中的运用,怎么理解C语言中的指针,如何运用?
  2. php session bug,thinkphp2.x中session的BUG及解决办法
  3. Python | 基于参数和返回值的功能分类
  4. tgc 什么意思 tgt_TGT的完整形式是什么?
  5. lcfirst_PHP lcfirst()函数与示例
  6. Java CharArrayReader mark()方法与示例
  7. 面试官:HashMap有几种遍历方法?推荐使用哪种?
  8. 设为首页 和 收藏本站js代码 兼容IE,chrome,ff
  9. Python操作excel(.xlsx)封装类MyPyExcel V2.0
  10. 安装composer以及laravel框架