什么是线程池

  • 是一种基于池化思想管理线程的工具。

    池化技术:池化技术简单点来说,就是提前保存大量的资源,以备不时之需。比如我们的对象池,数据库连接池等。

线程池好处

我们为什么要使用线程池,直接new thread start不好吗?

  • 降低资源消耗: 通过重复利用已创建的线程来降低线程创建和销毁所造成的消耗。
  • 提高响应速度: 任务到达时,可以立即执行,不需要等到线程创建再来执行任务。
  • 提高线程的可管理性: 线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

线程池的执行流程

我们先来看看线程池的一个执行流程图,此图来自文末参考1

通过上述图我们可以得出线程池执行任务可以有以下几种情况:

  • 如果当前的运行线程小于coreSize,则创建新线程来执行任务。
  • 如果当前运行的线程等于coreSize或多余coreSize(动态修改了coreSize才会出现这种情况),把任务放到阻塞队列中。
  • 如果队列已满无法将新加入的任务放进去的话,则需要创建新的线程来执行任务。
  • 如果新创建线程已经达到了最大线程数,任务将会被拒绝。

怎么是用线程池

java jdkExecutors有提供创建不同线程池的方法(一般不推荐这种做法)阿里巴巴的开发手册也明确强制规定不让通过Executors来创建的,在一些公司的开发规范里面应该也会有这么一条吧。

  • newFixedThreadPool
  • newSingleThreadExecutor
  • newCachedThreadPool
  • newScheduledThreadPool
  • newWorkStealingPool (jdk1.8新增的)

    我们可以使用ThreadPoolExecutor来创建线程池
  public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

我们可以看出创建线程池有七个参数,而上述我们通过Executors工具类来创建的线程池就一两个参数,其他参数它都帮我们默认写死了,我们只有真正理解了这几个参数才能更好的去是用线程池。下面我们来看看这七个参数(线程池参数)。

corePoolSize

  • 核心线程数(线程池的基本大小)当我们提交一个任务到线程池时就会创建一个线程来执行任务.当我们需要执行的任务数大于核心线程数了就不再创建,

    如果我们调用了prestartAllCoreThreads()方法线程池就会为我们提前创建好所有的基本线程。

maximumPoolSize

  • 最大线程数:线程池允许创建的最大线程数。如果队列已经满了,且已创建的线程数小于最大线程数,则线程池就会创建新的线程来执行任务。这里有个小知识点,如果我们的队列是用的无界队列,这个参数是不会起作用的,因为我们的任务会一直往队列中加,队列永远不会满(内存允许的情况)。

keepAliveTime

  • 空闲线程最大生存时间。当前线程数大于核心线程数时,结束多余的空闲线程等待新任务的最长时间。

    默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

    比如当前线程池中最大线程数(maximumPoolSize)为50,核心线程数(corePoolSize)为10,当前正在跑任务的线程数为30.然后是不是空出了20个线程没活干,所以这20个线程就要被消毁,有点卸磨杀驴的感觉。如果剩下的30个线程干完活了也休息了keepAliveTime这么久,然后这30个线程里面也要被销毁20个,就保留个核心线程。如果设置了allowCoreThreadTimeOut等于true核心线程也会被销毁。

    就跟我们做外包项目一样,甲方项目完成了就得去另外一个甲方,如果短时间内都没有甲方接纳你的话,你就要被辞退了,只会留下几个核心人员维护下项目,如果甲方项目维护的话用自己的人的话,所有的外包人会都会被辞退。

unit

  • 线程存活时间的的单位。可选的单位有dayshours等。

workQueue

任务队列。可以选择以下这些队列

threadFactory

用户设置创建线程的工厂,我们可以通过这个工厂来创建有业务意义的线程名字。我们可以对比下自定义的线程工厂和默认的线程工厂创创建的名字。

默认产生线程的名字 自定义线程工厂产生名字
pool-5-thread-1 testPool-1-thread-1
阿里开发手册也有明确说到,需要指定有意义的线程名字。

RejectedExecutionHandler

  • 线程池拒绝策略。当队列和线程池都满了说明线程池已经处于饱和状态。 必须要采取一定的策略来处理新提交的任务。jdk默认提供了四种拒绝策略:



    其实我们也可以自定义任务拒绝策略(实现下RejectedExecutionHandler接口),比如说如果任务拒绝了我们可以记录下日志,或者重试等,根据自己的业务需求来实现。
  • dubbo 任务拒绝策略
 @Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
String msg = String.format("Thread pool is EXHAUSTED!" +
" Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: "
+ "%d)," +
" Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(),
e.getLargestPoolSize(),
e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
url.getProtocol(), url.getIp(), url.getPort());
logger.warn(msg);
dumpJStack();
dispatchThreadPoolExhaustedEvent(msg);
throw new RejectedExecutionException(msg);
}

我们可以看出dubbo的拒绝策略主要记录了详细的级别为warm的日志、输出当前线程堆栈详情、继续抛出拒绝任务异常。

线程池参数如何设置?

线程池既然有这么多参数那么我们如何去根据自己的业务实际情况来去合理的设置每个参数?

  • 一般我们如果任务为耗时IO型比如读取数据库、文件读写以及网略通信的的话这些任务不会占据很多cpu的资源但是会比较耗时:线程数设置为2倍CPU数以上,充分的来利用CPU资源。
  • 一般我们如果任务为CPU密集型的话比如大量计算、解压、压缩等这些操作都会占据大量的cpu。所以针对于这种情况的话一般设置线程数为:1倍cpu+1。为啥要加1,很多说法是备份线程。
  • 如果既有IO密集型任务,又有CPU密集型任务,这种该怎么设置线程大小?这种的话最好分开用线程池处理,IO密集的用IO密集型线程池处理,CPU密集型的用cpu密集型处理。

    以上都只是理算情况下的估算而已,真正的合理参数还是需要看看实际生产运行的效果来合理的调整的。

监控线程池

  • 线程池工作是否饱和?线程的情况如何?总共执行了多少个任务?现在线程池的运行情况如何?队列里面是否有堆积任务?面对上面这些问题,线程池也有提供一些方法可以让我们来查看上面这些指标。



    有了这些参数我们是不是调整线程池的参数就更加方便了。或者根据线程池的活跃程度我们自动来调节(动态调整下篇再来说)线程池的参数。

关于线程池的几个问题

  • 线程池是否区分核心线程和非核心线程?
  • 如何保证核心线程不被销毁?
  • 线程池的线程是如何做到复用的?

    以上几个小问题我们去看看线程池的源码,这几个问题应该就不成问题了,我们下篇见。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。
  • 巨人肩膀摘苹果

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

    《java并发编程实战》

【Java并发编程】面试必备之线程池的更多相关文章

  1. Java 并发编程——Executor框架和线程池原理

    Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...

  2. [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors

    [Java并发编程(二)] 线程池 FixedThreadPool.CachedThreadPool.ForkJoinPool?为后台任务选择合适的 Java executors ... 摘要 Jav ...

  3. [Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ...

    [Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ... 摘要 介绍 Java 并发包里的几个主要 ExecutorService . 正文 ...

  4. Java 并发编程——Executor框架和线程池原理

    Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...

  5. Java并发编程之深入理解线程池原理及实现

    Java线程池在实际的应用开发中十分广泛.虽然Java1.5之后在JUC包中提供了内置线程池可以拿来就用,但是这之前仍有许多老的应用和系统是需要程序员自己开发的.因此,基于线程池的需求背景.技术要求了 ...

  6. Java并发编程、多线程、线程池…

    <实战java高并发程序设计>源码整理https://github.com/petercao/concurrent-programming/blob/master/README.md Ja ...

  7. Java并发编程(08):Executor线程池框架

    本文源码:GitHub·点这里 || GitEE·点这里 一.Executor框架简介 1.基础简介 Executor系统中,将线程任务提交和任务执行进行了解耦的设计,Executor有各种功能强大的 ...

  8. Java并发编程(十一)线程池的使用

    1.new Thread的弊端如下: a. 每次new Thread新建对象性能差. b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom. c. 缺乏更多 ...

  9. java并发编程(四) 线程池 & 任务执行、终止源码分析

    参考文档 线程池任务执行全过程:https://blog.csdn.net/wojiaolinaaa/article/details/51345789 线程池中断:https://www.cnblog ...

  10. Java并发编程:4种线程池和缓冲队列BlockingQueue

    一. 线程池简介 1. 线程池的概念: 线程池就是首先创建一些线程,它们的集合称为线程池.使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动 ...

随机推荐

  1. msvcp110.dll丢失修复 按我的方法来,保证修复!

    方法很简单,msvcp110.dll丢失,安装Microsoft Visual C++ 2012 Redistributable Package就可以,我把修复程序的链接放下面.链接地址: 链接:ht ...

  2. 朱朝兵《Web前端开发之HTML5+CSS3快速入门》课程资料在线学习

    <Web前端开发之HTML5+CSS3快速入门> [课程简介]本课程由一直专注前端技术与Discuz模板设计与制作!DZ起点网创始人朱朝兵出品,通过本课程学习,即可掌握html5的基本结构 ...

  3. Delphi 动态生成进度条窗体

    在implementation加入下面 uses Gauges; var Gauge1: TGauge; 加入Timer1控件,设为false procedure TForm1.Button1Clic ...

  4. DNS介绍与实现方法

    简介:域名系统(Domain Name System,缩写:DNS)是互联网的一项服务.是一种可以将域名和IP地址相互映射的以层次结构分布的数据库系,允许终端用户设备将给定的人类可读URL转换为网络可 ...

  5. 深度学习实战:从零构建图像分类API(Flask/FastAPI版)

    引言:AI时代的图像分类需求 在智能时代,图像分类技术已渗透到医疗影像分析.自动驾驶.工业质检等各个领域.作为开发者,掌握如何将深度学习模型封装为API服务,是实现技术落地的关键一步.本文将手把手教你 ...

  6. MySQL 的索引下推是什么?

    MySQL 的索引下推是什么? 索引下推(Index Condition Pushdown, ICP)是 MySQL 优化器在 InnoDB 存储引擎中引入的一种查询优化技术,从 MySQL 5.6 ...

  7. github项目收集

    web模块 Nginx 监控模块vts: https://github.com/vozlt/nginx-module-vts

  8. 安卓逆向学习及APK抓包(一)【Kali】安装 Scrcpy 进行手机投屏

    00X00 前言 官方给出的安装方式apt install scrcpy,但是经过各种换源操作啥的依然报错 ┌──(root㉿kali)-[~/桌面] └─# apt install scrcpy 正 ...

  9. CentOS安装msf

    cd /opt curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/met ...

  10. 【经验】you-get + ffmpeg|b站音频下载

    一.原理: you-get下载,ffmpeg音视频分离. 这两个都是命令行工具. you-get安装(无python环境请参考python详细安装教程): pip3 install --upgrade ...