百度
360搜索
搜狗搜索

threadpoolexecutor,线程池系列(3)ThreadPoolExecutor空闲线程的销毁原理详细介绍

本文目录一览: ThreadPoolExecutor详解

ThreadPoolExecutor、ctl、Worker、AQS、
任务调度
ThreadPoolExecutor
1、corePoolSize(线程池基本大小) 2、maximumPoolSize(线程池最大数量) 3、workQueue(任务队列):用于保存等待执行的阻塞队列 ArrayBlockingQueue、LinkedBlockingQueue、 SynchronousQueue、PriorityBlockingQueue 4、RejectedExecutionHandler(饱和策略)(interface支持自定义策略) AbortPolicy(默认):直接抛出异常 CallerRunsPolicy:只用调用者所在的线程来运行任务 DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务 DiscardPolicy:不处理,丢弃掉

ThreadPoolExcutor用法详解

java线程池用法举例:

1、ThreadPoolExecutor executor =new ThreadPoolExecutor(2,10,30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100));

2、ThreadPoolExecutor executor2 =new ThreadPoolExecutor(2,10,30, TimeUnit.SECONDS,new LinkedBlockingDeque<>());

知道了各个参数的作用后,我们开始构造符合我们期待的线程池。首先看JDK给我们预定义的几种线程池:

一、预定义线程池

FixedThreadPool

publicstaticExecutorServicenewFixedThreadPool(intnThreads){returnnewThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,newLinkedBlockingQueue());? ? }

corePoolSize与maximumPoolSize相等,即其线程全为核心线程,是一个固定大小的线程池,是其优势;

keepAliveTime = 0 该参数默认对核心线程无效,而FixedThreadPool全部为核心线程;

workQueue 为LinkedBlockingQueue(无界阻塞队列),队列最大值为Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。是其劣势;

FixedThreadPool的任务执行是无序的;

适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。

CachedThreadPool

publicstaticExecutorServicenewCachedThreadPool(){returnnewThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,newSynchronousQueue());? ? }

corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制;

keepAliveTime = 60s,线程空闲60s后自动结束。

workQueue 为 SynchronousQueue 同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,因为CachedThreadPool线程创建无限制,不会有队列等待,所以使用SynchronousQueue;

适用场景:快速处理大量耗时较短的任务,如Netty的NIO接受请求时,可使用CachedThreadPool。

SingleThreadExecutor

publicstaticExecutorServicenewSingleThreadExecutor(){returnnewFinalizableDelegatedExecutorService? ? ? ? ? ? (newThreadPoolExecutor(1,1,0L, TimeUnit.MILLISECONDS,newLinkedBlockingQueue()));? ? }

咋一瞅,不就是newFixedThreadPool(1)吗?定眼一看,这里多了一层FinalizableDelegatedExecutorService包装,这一层有什么用呢,写个dome来解释一下:

publicstaticvoidmain(String[] args){? ? ? ? ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1);? ? ? ? ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService;? ? ? ? System.out.println(threadPoolExecutor.getMaximumPoolSize());? ? ? ? threadPoolExecutor.setCorePoolSize(8);? ? ? ? ? ? ? ? ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();//? ? ? 运行时异常 java.lang.ClassCastException//? ? ? ThreadPoolExecutor threadPoolExecutor2 = (ThreadPoolExecutor) singleExecutorService;}

对比可以看出,FixedThreadPool可以向下转型为ThreadPoolExecutor,并对其线程池进行配置,而SingleThreadExecutor被包装后,无法成功向下转型。 因此,SingleThreadExecutor被定以后,无法修改,做到了真正的Single。

ScheduledThreadPool

publicstaticScheduledExecutorServicenewScheduledThreadPool(intcorePoolSize){returnnewScheduledThreadPoolExecutor(corePoolSize);? ? }

newScheduledThreadPool调用的是ScheduledThreadPoolExecutor的构造方法,而ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,构造是还是调用了其父类的构造方法。

publicScheduledThreadPoolExecutor(intcorePoolSize){super(corePoolSize, Integer.MAX_VALUE,0, NANOSECONDS,newDelayedWorkQueue());? ? }

二、自定义线程池

线程池工作机制

ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务
corePoolSize 核心线程数
maximumPoolSize 最大线程数
线程池中允许的最大线程数。
keepAliveTime 线程空闲时的存活时间
当线程没有执行任务时,继续存活的时间。当线程池中的线程数量大于核心线程数时,即时没有新任务提交,核心线程外的线程也不会立即销毁,而是等待keepAliveTime才会销毁。
unit 线程空闲时的存活时间单位
workQueue 阻塞队列
阻塞队列:1)支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满;2)支持阻塞的移除方法:在队列为空时,获取元素的线程会等待队列变为非空
生产者和消费者模式 能够解决并发问题,通过平衡生产线程和消费线程的工作能力来提高程序整体处理数据的速度。通过阻塞队列来进行通信,生产者生产完数据不用等待消费者处理,直接扔给阻塞队列,消费者也直接从阻塞队列中取数据,既能够解耦,又平衡两者的处理能力。
常用阻塞队列:
threadFactory 创建线程的工厂
handler 拒绝策略
当阻塞队列满了,且没有空闲的工作线程,继续提交任务会采取一种策略处理新任务。线程池提供了4中策略:
1)AbortPolicy:直接抛出异常,默认策略
2)CallerRunsPolicy:用调用者所在的线程来执行任务
3)DiscardPolicy:直接丢弃任务
4)DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务
也可以自己根据应用场景(如记录日志或持久化储存不能处理的任务)实现RejectedExecutionHandler接口,自定义拒绝策略。
看下ThreadPoolExecutor的execute方法:
流程图:
提交任务:
关闭线程池:遍历线程池中的工作线程,然后逐个调用interrupt方法来中断线程。

线程池系列(3)ThreadPoolExecutor空闲线程的销毁原理

源码:版本JDK8
JDK线程池提供了一些核心参数,用于空闲maximum线程的销毁和空闲core线程的销毁。
工作线程会不断的去阻塞队列中拉取任务,此处有两个方法:
线程池销毁核心线程,依赖的API是queue的poll。当在keepAliveTime时间内拉取不到任务,则会中断工作线程的while循环,开始销毁任务。但是最终是否要销毁线程,还取决于阻塞队列中是否为空。
想想一个场景: 线程池:只有一个核心线程。
线程池系列(1)ThreadPoolExecutor线程池参数以及使用 线程池系列(2) ThreadPoolExecutor的实现原理(源码分析)
灯下黑?原来线程池是最典型生产消费者模式

Android线程池ThreadPoolExecutor详解

???????传统的多线程是通过继承Thread类及实现Runnable接口来实现的,每次创建及销毁线程都会消耗资源、响应速度慢,且线程缺乏统一管理,容易出现阻塞的情况,针对以上缺点,线程池就出现了。
???????线程池是一个创建使用线程并能保存使用过的线程以达到复用的对象,简单的说就是一块缓存了一定数量线程的区域。
???????1.复用线程:线程执行完不会立刻退出,继续执行其他线程; ???????2.管理线程:统一分配、管理、控制最大并发数;
???????1.降低因频繁创建&销毁线程带来的性能开销,复用缓存在线程池中的线程; ???????2.提高线程执行效率&响应速度,复用线程:响应速度;管理线程:优化线程执行顺序,避免大量线程抢占资源导致阻塞现象; ???????3.提高对线程的管理度;
???????线程池的使用也比较简单,流程如下:
???????接下来通过源码来介绍一下ThreadPoolExecutor内部实现及工作原理。
???????线程池的最终实现类是ThreadPoolExecutor,通过实现可以一步一步的看到,父接口为Executor:
???????其他的继承及实现关系就不一一列举了,直接通过以下图来看一下:
???????从构造方法开始看:
???????通过以上可以看到,在创建ThreadPoolExecutor时,对传入的参数是有要求的:corePoolSize不能小于0;maximumPoolSize需要大于0,且需要大于等于corePoolSize;keepAliveTime大于0;workQueue、threadFactory都不能为null。 ???????在创建完后就需要执行Runnable了,看以下execute()方法:
???????在execute()内部主要执行的逻辑如下: ???????分析点1:如果当前线程数未超过核心线程数,则将runnable作为参数执行addWorker(),true表示核心线程,false表示非核心线程; ???????分析点2:核心线程满了,如果线程池处于运行状态则往workQueue队列中添加任务,接下来判断是否需要拒绝或者执行addWorker(); ???????分析点3:以上都不满足时 [corePoolSize=0且没有运行的线程,或workQueue已经满了] ,执行addWorker()添加runnable,失败则执行拒绝策略; ??????? 总结一下:线程池对线程创建的管理,流程图如下:

???????在执行addWorker时,主要做了以下两件事: ???????分析点1:将runnable作为参数创建Worker对象w,然后获取w内部的变量thread; ???????分析点2:调用start()来启动thread; ???????在addWorker()内部会将runnable作为参数传给Worker,然后从Worker内部读取变量thread,看一下Worker类的实现:
???????Worker实现了Runnable接口,在Worker内部,进行了赋值及创建操作,先将execute()时传入的runnable赋值给内部变量firstTask,然后通过ThreadFactory.newThread(this)创建Thread,上面讲到在addWorker内部执行t.start()后,会执行到Worker内部的run()方法,接着会执行runWorker(this),一起看一下:
???????前面可以看到,runWorker是执行在子线程内部,主要执行了三件事: ???????分析1:获取当前线程,当执行shutdown()时需要将线程interrupt(),接下来从Worker内部取到firstTask,即execute传入的runnable,接下来会执行; ???????分析2:while循环,task不空直接执行;否则执行getTask()去获取,不为空直接执行; ???????分析3:对有效的task执行run(),由于是在子线程中执行,因此直接run()即可,不需要start(); ???????前面看到,在while内部有执行getTask(),一起看一下:
???????getTask()是从workQueue内部获取接下来需要执行的runnable,内部主要做了两件事: ???????分析1:先获取到当前正在执行工作的线程数量wc,通过判断allowCoreThreadTimeOut[在创建ThreadPoolExecutor时可以进行设置]及wc > corePoolSize来确定timed值; ???????分析2:通过timed值来决定执行poll()或者take(),如果WorkQueue中有未执行的线程时,两者作用是相同的,立刻返回线程;如果WorkQueue中没有线程时,poll()有超时返回,take()会一直阻塞;如果allowCoreThreadTimeOut为true,则核心线程在超时时间没有使用的话,是需要退出的;wc > corePoolSize时,非核心线程在超时时间没有使用的话,是需要退出的; ???????allowCoreThreadTimeOut是可以通过以下方式进行设置的:
???????如果没有进行设置,那么corePoolSize数量的核心线程会一直存在。 ??????? 总结一下:ThreadPoolExecutor内部的核心线程如何确保一直存在,不退出? ???????上面分析已经回答了这个问题,每个线程在执行时会执行runWorker(),而在runWorker()内部有while()循环会判断getTask(),在getTask()内部会对当前执行的线程数量及allowCoreThreadTimeOut进行实时判断,如果工作数量大于corePoolSize且workQueue中没有未执行的线程时,会执行poll()超时退出;如果工作数量不大于corePoolSize且workQueue中没有未执行的线程时,会执行take()进行阻塞,确保有corePoolSize数量的线程阻塞在runWorker()内部的while()循环不退出。 ???????如果需要关闭线程池,需要如何操作呢,看一下shutdown()方法:
???????以上可以看到,关闭线程池的原理:a. 遍历线程池中的所有工作线程;b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止) ???????也可调用shutdownNow()来关闭线程池,二者区别: ???????shutdown():设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程; ???????shutdownNow():设置线程池的状态为STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表; ???????使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow(); ??????? 总结一下:ThreadPoolExecutor在执行execute()及shutdown()时的调用关系,流程图如下:

???????线程池可以通过Executors来进行不同类型的创建,具体分为四种不同的类型,如下:
???????可缓存线程池:不固定线程数量,且支持最大为Integer.MAX_VALUE的线程数量:
???????1、线程数无限制 ???????2、有空闲线程则复用空闲线程,若无空闲线程则新建线程 ???????3、一定程度上减少频繁创建/销毁线程,减少系统开销
???????固定线程数量的线程池:定长线程池
???????1、可控制线程最大并发数(同时执行的线程数) ???????2、超出的线程会在队列中等待。
???????单线程化的线程池:可以理解为线程数量为1的FixedThreadPool
???????1、有且仅有一个工作线程执行任务 ???????2、所有任务按照指定顺序执行,即遵循队列的入队出队规则
???????定时以指定周期循环执行任务
???????一般来说,等待队列 BlockingQueue 有: ArrayBlockingQueue 、 LinkedBlockingQueue 与 SynchronousQueue 。 ???????假设向线程池提交任务时,核心线程都被占用的情况下: ??????? ArrayBlockingQueue :基于数组的阻塞队列,初始化需要指定固定大小。 ???????当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以最终可能出现后提交的任务先执行,而先提交的任务一直在等待。 ??????? LinkedBlockingQueue :基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。 ???????当指定大小后,行为就和 ArrayBlockingQueue一致。而如果未指定大小,则会使用默认的 Integer.MAX_VALUE 作为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。 ??????? SynchronousQueue :无容量的队列。 ???????使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合Integer.MAX_VALUE就实现了真正的无等待。 ???????但是需要注意的是, 进程的内存是存在限制的,而每一个线程都需要分配一定的内存。所以线程并不能无限个。

Python ThreadPoolExecutor 异常中止解决方案

通常情况,我们利用 Ctrl+C 让程序触发 KeyboardInterrupt 异常,中止程序运行。线程池方案下, Ctrl-C 失效,当线程池里的线程任务跑完后,才会触发 KeyboardInterrupt 。
上下文管理协议相当于隐性地省略了 threadPool.shutdown(wait=True) ,同时,程序正常执行完成或出现异常中断的时候,就会调用 __exit__() 方法,接下来进行异常中止的基础。
适用于 Django 等 WEB 应用框架,本身自带多线程,修改全局变量简单,但要注意线程安全。
程序运行中,只需 sign = 1 或者 exiting.set() ,worker 函数则跳过主要运算部分,剩余线程任务将迅速完成,变相达到中止多线程任务的目的。
提交给线程池的每个线程任务 task 加入 threadPool 中,方便后续对 task 进行操作。当 for 循环内的 task 全部提交后,线程会再后台运行,而进程运行至 while 中堵塞,直至 threadPool 中最后一个线程是否 .done() 。若进程堵塞在 while 中接收到 Ctrl+C 的 KeyboardInterrupt 异常,则从后往前取消 threadPool 中所有任务,达到中止目的。

阅读更多 >>>  异步fifo读写位宽不一致,异步fifo实现

【Java基础】线程池的原理是什么?

1. 什么是线程池?
很简单,简单看名字就知道是装有线程的池子,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的复用。
2. 线程池的好处
我们知道不用线程池的话,每个线程都要通过new Thread(xxRunnable).start()的方式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的CPU和内存资源,也会造成GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。
3. 线程池核心类
在java.util.concurrent包中我们能找到线程池的定义,其中ThreadPoolExecutor是我们线程池核心类,
什么是线程池?
总归为:池化技术 ---》数据库连接池 缓存架构 缓存池 线程池 内存池,连接池,这种思想演变成缓存架构技术---> JDK设计思想有千丝万缕的联系
首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然后再讲述它的实现原理,接着给出了它的使用示例,最后讨论了一下如何合理配置线程池的大小。
Java 中的 ThreadPoolExecutor 类
java.uitl.concurrent.ThreadPoolExecutor 类是线程池中最核心的一个类,因此如果要透彻地了解Java 中的线程池,必须先了解这个类。下面我们来看一下 ThreadPoolExecutor 类的具体实现源码。
在 ThreadPoolExecutor 类中提供了四个构造方法:
从上面的代码可以得知,ThreadPoolExecutor 继承了 AbstractExecutorService 类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
下面解释下一下构造器中各个参数的含义:
corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads() 或者 prestartCoreThread()方法,从这 2 个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建 corePoolSize 个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于 corePoolSize 时,keepAliveTime 才会起作用,直到线程池中的线程数不大于 corePoolSize,即当线程池中的线程数大于 corePoolSize 时,如果一个线程空闲的时间达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。但是如果调用了 allowCoreThreadTimeOut(boolean) 方法,在线程池中的线程数不大于 corePoolSize 时,keepAliveTime 参数也会起作用,直到线程池中的线程数为0;
unit:参数 keepAliveTime 的时间单位,有 7 种取值,在 TimeUnit 类中有 7 种静态属性:
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue 和 PriorityBlockingQueue 使用较少,一般使用 LinkedBlockingQueue 和 Synchronous。线程池的排队策略与 BlockingQueue 有关。
threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略,有以下四种取值:
具体参数的配置与线程池的关系将在下一节讲述。
从上面给出的 ThreadPoolExecutor 类的代码可以知道,ThreadPoolExecutor 继承了AbstractExecutorService,我们来看一下 AbstractExecutorService 的实现:
AbstractExecutorService 是一个抽象类,它实现了 ExecutorService 接口。
我们接着看 ExecutorService 接口的实现:
而 ExecutorService 又是继承了 Executor 接口,我们看一下 Executor 接口的实现:

ThreadPoolExecutor线程池问题,为什么没有实现Runnable接口,execute入参可以这样写?如下

您可以使用 Java 8 的 Lambda 表达式实现接口的实例化,方法引用也可以作为 Lambda 表达式的一部分。在上面的示例中,Person::getSddd 方法就是一个 Lambda 表达式,它实现了 Runnable 接口,因此 Person 类无需实现 Runnable 接口也可以使用 executor 来执行该类中的方法。
ThreadPoolExecutor线程池允许将任务传递给线程而无需实现Runnable接口,因此可以使用函数式编程语法(如lambda表达式和方法引用)来提交工作。在上面的示例中,Person::getSddd表示调用Person类中的静态方法getSddd,这个方法本质上就是Runnable接口的实现,因此可以直接被ThreadPoolExecutor线程池执行。
execute方法是ThreadPoolExecutor类中的一个方法,它需要一个Runnable对象作为其参数。Runnable只是一个具有可以被调用的run方法的对象。在Person类中的getSddd方法没有run方法,但是它正在使用方法引用作为参数传递给execute方法。
方法引用是创建调用现有方法的lambda表达式的简写方式,在这种情况下,即使Person类没有实现Runnable接口,也可以将getSddd方法的方法引用作为Runnable传递给execute方法。
当调用execute方法时,它会创建一个新线程,并在该线程中调用getSddd方法,从而使getSddd方法并发运行。

什么是线程池?为什么要使用线程池?如何使用?

线程池其实就是将多个线程对象放到一个容器当中。

可以重用线程,减少创建和销毁线程带来的消耗。

要想知道如何使用线程池,就要先知道线程池的种类有多少种?线程池大概有以下几种:

以下介绍这几种线程池的用法:

ThreadPoolExecutor 是线程池真正的实现方法,以下是 ThreadPoolExecutor 的构造方法:

它需要传入一系列参数来配置线程池,下面介绍每个参数的意义。

一种固定线程数量的线程池。

可以通过 Executors 的 newFixedThreadPool() 方法创建:

newFixedThreadPool() 具体实现:

可以看出 newFixedThreadPool() 是通过创建 ThreadPoolExecutor 来创建线程池的。

并且因为 corePoolSize 和 maximumPoolSize 是一样的,所以这种线程池只有核心线程,任务超出线程数后,会在队列中等待。

具体使用如下:

一种线程数量不定的线程池。

可以通过 Executors 的 newCachedThreadPool() 方法创建:

newCachedThreadPool() 具体实现:

可以看到 corePoolSize 为 0,maximumPoolSize 为 Integer.MAX_VALUE,证明这种线程池没有核心线程,但是有多个非核心线程。

这种线程池的特点就是,当有任务提交时,如果有空闲线程则复用空闲线程,没有的话就新建线程处理。

空闲线程如果超过 60 秒就会被回收。

具体使用如下:

一种只有一个工作线程的线程池。

可以通过 Executors 的 newSingleThreadExecutor() 方法创建:

newSingleThreadExecutor() 具体实现:

从源码可以看出,这种线程池只有一个核心线程,并且总线程数为 1。

具体使用如下:

一种核心线程数量固定,非核心线程数不固定的线程池。

可以通过 Executors 的 newScheduledThreadPool() 方法创建:

newScheduledThreadPool() 具体实现:

从源码可以看出这种线程池的核心线程是固定的,非核心线程数没有限制,但是非核心线程出现空闲后,10 毫秒就会被回收。

具体使用:

线程池 ExecutorService

并发,启动大量任务线程时,频繁的线程创建/销毁会造成浪费。(创建、运行、销毁都需要时间),采用线程池,线程复用技术,提高性能。
线程池实现类,ThreadPoolExecutor 类。
ThreadPoolExecutor 构造方法,实现不同类型线程池。
corePoolSize,核心线程数。 maximumPoolSize,允许的最大线程,超过报异常。 keepAliveTime,非核心线程活跃时间。 TimeUnit,时间度量。 BlockingQueue

,任务队列,(无限、有限队列、栈)。 ThreadFactory,线程工厂。

不推荐直接使用 ThreadPoolExecutor 构造方法创建。

1,缓存

核心线程是0,最大线程 MAX_VALUE ,任务队列 SynchronousQueue 是一个管道,不存储,线程活跃时间60秒。 适用短期大量任务的场景。 某一时间段高并发任务,创建大量线程,任务结束后,线程变空闲,60s以内重用,处理新任务,空闲到达60s,销毁。

2,固定数量

核心线程、最大线程,自定义,LinkedBlockingQueue 任务队列,不设置空闲时间,无界队列。 适用长期任务场景。 任务队列无界,仅核心线程工作,keepAlieveTime 不需要设置。

3,单线程

核心线程、最大线程,数量1,LinkedBlockingQueue 任务队列,不设置空闲时间,无界队列。 适用单个任务顺序执行场景。 只有一个核心线程,新任务加入队列。

4,定时任务

ScheduledThreadPoolExecutor 实例,ThreadPoolExecutor 子类。 核心线程自定义,最大线程 MAX_VALUE,DelayedWorkQueue 任务队列,空闲时间10s。 适用指定有延迟的任务或周期性重复任务场景。 队列不是按照 submit 时间排序,以延时时间为优先级升序排序,延时时间短的,优先级高,先执行。

线程池 ThreadPoolExecutor 创建,将任务派发给线程池,execute() 方法,自动分配线程执行。

workerCountOf(c) 方法,判断工作线程数量,当

= 核心线程,isRunning(c) 方法,表示 c<0,offer()方法,将任务加入队列。 offer() 是非阻塞方法,如果队列已满,返回失败,此时,addWorker() 方法,创建线程,不设标志位参数代表非核心线程,任务将由新建的非核心线程处理。

新线程创建 如果工作线程 >CAPACITY 容量,或 >= 允许的最大值(创建核心线程 >= 核心线程数量),失败返回。

创建一个 Runnable 类型 Worker 对象,ThreadPoolExecutor 内部静态类,用户任务封装,newThread() 方法,创建新线程,将 Worker(this) 作为新线程任务主体。

将 Worker 任务加入 HashSet 集合,设置 workerAdded 标志,启动新线程( start方法),设置 workerStarted 启动标志,代表线程启动,执行 Worker 任务的 run() 方法,调用 runWorker() 方法(外部类方法)。

新线程执行 Worker 任务的 run() 方法,借助 Worker 开始为线程池工作,从 Worker 获取内部 Runnable(即 execute 派送的用户任务),并执行 run() 方法。 用户任务处理完成,线程不会马上结束,while 循环,继续 getTask() 方法,从任务队列中获取任务,该方法可能会导致阻塞,队列空则线程结束。

BlockingQueue 阻塞队列。

1,工作线程 wc > 核心线程 设置 timed 标志,队列采用阻塞等待,(poll + timeout方式),timeout 设置线程 keepAliveTime 时间 。 因此,即使队列没有任务,线程仍然存活,(任务进队列后可立即唤醒展开工作)。

2,工作线程 wc < 核心线程 (仅有核心线程),队列采用一直阻塞,( take 方式),队列是空时,线程一直阻塞,核心线程不会结束。

3,队列空,poll 超时 设置 timeOut 已超时标志,下次循环时,如果再发生工作线程 wc > 核心线程 ( timed 和 timedOut 标志并存),线程数量自减,退出 while 返回空,线程结束。

4,设置核心线程 allowCoreThreadTimeOut 不管工作线程 wc,采用 poll + timeout 方式,keepAliveTime 队列无任务,所有线程都会 timedOut 超时标志,下次循环自动结束。 即使 wc < 核心线程,线程也会结束退出,允许核心线程超时结束。

线程池本质,设置一定数量的线程集合,任务结束,但线程不结束,根据设定值,请求任务队列,继续任务,复用线程。

线程等待,利用任务队列的阻塞特性。阻塞任务队列,访问空队列时,线程等待,timeout 超时时间,实现线程 keepAliveTime 活动或一直存活。

任务队列 poll() 方法,取队列,非阻塞,poll + timeout,阻塞 timeout 时间。 take() 方法,取队列,阻塞等待。 put() 方法,存队列,阻塞等待, offer() 方法,存队列,非阻塞,offer + timeout,阻塞 timeout 时间。

核心线程,在线程集合中,未具体标明,若 All 线程都完成任务,空闲,且队列空,在 getTask() 方法,根据超时时间,逐一唤醒,结束,剩下的数量 = 核心,它们即核心。

任重而道远

阅读更多 >>>  threadlocal内存泄露,ThreadLocal详解

网站数据信息

"threadpoolexecutor,线程池系列(3)ThreadPoolExecutor空闲线程的销毁原理"浏览人数已经达到19次,如你需要查询该站的相关权重信息,可以点击进入"Chinaz数据" 查询。更多网站价值评估因素如:threadpoolexecutor,线程池系列(3)ThreadPoolExecutor空闲线程的销毁原理的访问速度、搜索引擎收录以及索引量、用户体验等。 要评估一个站的价值,最主要还是需要根据您自身的需求,如网站IP、PV、跳出率等!