首页 > 其他分享 >线程池工作原理和实现原理

线程池工作原理和实现原理

时间:2024-02-10 11:33:24浏览次数:30  
标签:队列 实现 创建 corePoolSize 任务 线程 原理 ThreadPoolExecutor

 

为什么要使用线程池

不使用线程池会怎么样?当需要多线程并发执行任务时,只能不断的通过new Thread创建线程,每创建一个线程都需要在堆上分配内存空间,同时需要分配虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间,当这个线程对象被可达性分析算法标记为不可用时被GC回收,这样频繁的创建和回收需要大量的额外开销。再者说,JVM的内存资源是有限的,如果系统中大量的创建线程对象,JVM很可能直接抛出OutOfMemoryError异常,还有大量的线程去竞争CPU会产生其他的性能开销,更多的线程反而会降低性能,所以必须要限制线程数。

既然不使用线程池有那么多问题,我们来看一下使用线程池有哪些好处:

  • 使用线程池可以复用池中的线程,不需要每次都创建新线程,减少创建和销毁线程的开销;
  • 同时,线程池具有队列缓冲策略、拒绝机制和动态管理线程个数,特定的线程池还具有定时执行、周期执行功能,比较重要的一点是线程池可实现线程环境的隔离,例如分别定义支付功能相关线程池和优惠券功能相关线程池,当其中一个运行有问题时不会影响另一个。

如何构造一个线程池对象

本文内容我们只聊线程池ThreadPoolExecutor,查看它的源码会发现它继承了AbstractExecutorService抽象类,而AbstractExecutorService实现了ExecutorService接口,ExecutorService继承了Executor接口,所以ThreadPoolExecutor间接实现了ExecutorService接口和Executor接口,它们的关系图如下。

一般我们使用的execute方法是在Executor接口中定义的,而submit方法是在ExecutorService接口中定义的,所以当我们创建一个Executor类型变量引用ThreadPoolExecutor对象实例时可以使用execute方法提交任务,当我们创建一个ExecutorService类型变量时可以使用submit方法,当然我们可以直接创建ThreadPoolExecutor类型变量使用execute方法或submit方法。

ThreadPoolExecutor定义了七大核心属性,这些属性是线程池实现的基石。

  • corePoolSize(int):核心线程数量。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到任务队列当中。线程池将长期保证这些线程处于存活状态,即使线程已经处于闲置状态。除非配置了allowCoreThreadTimeOut=true,核心线程数的线程也将不再保证长期存活于线程池内,在空闲时间超过keepAliveTime后被销毁。
  • workQueue:阻塞队列,存放等待执行的任务,线程从workQueue中取任务,若无任务将阻塞等待。当线程池中线程数量达到corePoolSize后,就会把新任务放到该队列当中。JDK提供了四个可直接使用的队列实现,分别是:基于数组的有界队列ArrayBlockingQueue、基于链表的无界队列LinkedBlockingQueue、只有一个元素的同步队列SynchronousQueue、优先级队列PriorityBlockingQueue。在实际使用时一定要设置队列长度。
  • maximumPoolSize(int):线程池内的最大线程数量,线程池内维护的线程不得超过该数量,大于核心线程数量小于最大线程数量的线程将在空闲时间超过keepAliveTime后被销毁。当阻塞队列存满后,将会创建新线程执行任务,线程的数量不会大于maximumPoolSize。
  • keepAliveTime(long):线程存活时间,若线程数超过了corePoolSize,线程闲置时间超过了存活时间,该线程将被销毁。除非配置了allowCoreThreadTimeOut=true,核心线程数的线程也将不再保证长期存活于线程池内,在空闲时间超过keepAliveTime后被销毁。
  • TimeUnit unit:线程存活时间的单位,例如TimeUnit.SECONDS表示秒。
  • RejectedExecutionHandler:拒绝策略,当任务队列存满并且线程池个数达到maximunPoolSize后采取的策略。ThreadPoolExecutor中提供了四种拒绝策略,分别是:抛RejectedExecutionException异常的AbortPolicy(如果不指定的默认策略)、使用调用者所在线程来运行任务CallerRunsPolicy、丢弃一个等待执行的任务,然后尝试执行当前任务DiscardOldestPolicy、不动声色的丢弃并且不抛异常DiscardPolicy。项目中如果为了更多的用户体验,可以自定义拒绝策略。
  • threadFactory:创建线程的工厂,虽说JDK提供了线程工厂的默认实现DefaultThreadFactory,但还是建议自定义实现最好,这样可以自定义线程创建的过程,例如线程分组、自定义线程名称等。

一般我们使用类的构造方法创建它的对象,ThreadPoolExecutor提供了四个构造方法。

可以看到前三个方法最终都调用了最后一个、参数列表最长的那个方法,在这个方法中给七个属性赋值。创建线程池对象,强烈建议通过使用ThreadPoolExecutor的构造方法创建,不要使用Executors,至于建议的理由上文中也有说过,这里再引用阿里《Java开发手册》中的一段描述。

 

参考链接 https://cloud.tencent.com/developer/article/1634249

线程池工作原理

关于线程池的工作原理,我用下面的7幅图来展示。 1.通过execute方法提交任务时,当线程池中的线程数小于corePoolSize时,新提交的任务将通过创建一个新线程来执行,即使此时线程池中存在空闲线程。

2.通过execute方法提交任务时,当线程池中线程数量达到corePoolSize时,新提交的任务将被放入workQueue中,等待线程池中线程调度执行。

3.通过execute方法提交任务时,当workQueue已存满,且maximumPoolSize大于corePoolSize时,新提交的任务将通过创建新线程执行。

4.当线程池中的线程执行完任务空闲时,会尝试从workQueue中取头结点任务执行。

5.通过execute方法提交任务,当线程池中线程数达到maxmumPoolSize,并且workQueue也存满时,新提交的任务由RejectedExecutionHandler执行拒绝操作。

6.当线程池中线程数超过corePoolSize,并且未配置allowCoreThreadTimeOut=true,空闲时间超过keepAliveTime的线程会被销毁,保持线程池中线程数为corePoolSize。

注意:上图表达的是销毁空闲线程,保持线程数为corePoolSize,不是销毁corePoolSize中的线程。

7.当设置allowCoreThreadTimeOut=true时,任何空闲时间超过keepAliveTime的线程都会被销毁。

线程池底层实现原理

线程池处在不同的状态时,它的处理能力是不同的。

线程池不同状态之间的转换时机及转换关系如下图。

可能会疑问“为什么要介绍上面这些?”,这是因为接下来的源码分析会用到这些基础的知识点。一般,我们使用ThreadPoolExecutor的execute方法提交任务,所以从execute的源码入手。

为了更轻松的理解上图中的源码,我又画了一个流程图。

到这

标签:队列,实现,创建,corePoolSize,任务,线程,原理,ThreadPoolExecutor
From: https://www.cnblogs.com/JavaYuYin/p/18012766

相关文章

  • 关于刘谦2024春晚的数学游戏原理
    自己想出来的!首先牌的顺序肯定是形如\(ABCDABCD\)。将牌的顺序考虑成一个字符环。按照名字长度对该字符环进行左移,本质上没有打乱这个环的顺序。因此在置换后,牌的顺序还是会形如\(ABCDABCD\)。将前三张随机放到牌堆中间,我们发现此时牌堆顶和牌堆底的两张牌是一样的。因此......
  • 在k8S中,初始化容器(init container)概念原理是什么?
    在Kubernetes(k8S)中,初始化容器(InitContainer)是一个特殊类型的容器,它会在应用程序容器启动之前运行。它的主要目的是执行一些必要的先决条件任务,这些任务必须在主应用容器开始服务前完成。初始化容器的概念原理如下:顺序执行:Pod中可以定义多个初始化容器,它们按照配置文件......
  • 为什么要有线程?
    这是我学习时一直想不通的问题。尽管很多书籍和视频都做了解释,但由于没有实际的案例和场景,导致我不能真正明白这个问题。接下来说下我的迷思,以及迷思的答案。线程可以有效配合多核处理器,提高效率。这个是最广泛的说法,对于网络服务器,对于每个请求创建一个线程,可以CPU同时处理多个......
  • Python实现视频片头和片尾添加(不实用)
    参考的原代码,运行提示:RuntimeError:imageio.ffmpeg.download()hasbeendeprecated.Use'pipinstallimageio-ffmpeg'instead.'直接删除掉这行:imageio.plugins.ffmpeg.download()改为:importimageiofromdatetimeimportdatetimeimportosfrommoviepy.video.......
  • 【JDK】Random 的局限以及ThreadLocalRandom 类原理剖析
    1 前言我们平时使用随机数大家可能会用到 Random,但是它的问题大家知道吗?以及该如何解决呢?这节我们就来看看。2  Random类及其局限性在JDK7之前包括现在,java.util.Random都是使用比较广泛的随机数生成工具类,而且java.lang.Math中的随机数生成也使用的是java.util.......
  • Android Studio 只启动安卓模拟器的脚本实现
    基本上都是参考:https://blog.csdn.net/qq_39970857/article/details/122186784一.找到SDK安装路径这俩张懒得画图,是偷的)二.win+r打开cmd(反正不用管理权限,随便怎么打开)......
  • VRRP原理与配置
    VRRP(VirtualRouterRedundancyProtocol,虚拟路由器冗余协议)既能够实现网关的备份,又能解决多个网关之间互相冲突的问题,从而提高网络可靠性。VRRP概述VRRP的基本概念(1)VRRP报文格式VRRP报文字段含义如下:Ver:VRRP目前有两个版本,其中VRRPv2仅适用于IPv4网络,VRRPv3适用于IP......
  • 手写实现cni插件
    k8sv1.19.0mycni配置文件cat>>/etc/cni/net.d/mycni.json<<EOF{"cniVersion":"0.2.0","name":"mycni","type":"mycni"}EOFtype对应/opt/cni/bin目录下二进制文件。mycni代码并编译mkdir/run/n......
  • linux shell中实现对fastq read重命名
     001、命令程序:##step1:处理read1awk'END{tmp=NR/4;split(FILENAME,a,"_");for(i=1;i<=tmp;i++){print"@"a[1]"."i,i"/1"}}'name_1.clean.fastq>name_1.list##生成read名awk'{if(N......
  • Python实现软件设计模式10:装饰器模式 Decorator Pattern
    概念是一种对象结构型模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为是一种用于替代继承的技术,他通过一种无须定义子类的方式给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增......