这里主要总结池子的管理线程、处理任务的流程,以采用LinkedBlockingQueue corePoolSize=maxPoolSzie=5 KeepAliveTime=0L 为例解说:
ThreadPoolExecutor .execute(Runnable),向池中存放任务;随着execute 到corePoolsize任务时,池初始化启动了corePoolsize个线程,池子维护这5个线程;这5个线程监控Queue等待take任务。
当corePoolSzie线程就绪后,新进任务直接queue.offer();
/**
* Main run loop
*/
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
池中线程,一直处于dotask ...queue.take()...dotask的循环之中。而queue.take()和poll()的区别就是直到取到有效element才返回;
引用别人解说:
所有 BlockingQueue
都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。(什么意思?如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)
如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
排队有三种通用策略:
-
直接提交。工作队列的默认选项是
SynchronousQueue
,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
-
无界队列。使用无界队列(例如,不具有预定义容量的
LinkedBlockingQueue
)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
-
有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如
ArrayBlockingQueue
)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
例子一:使用直接提交策略,也即SynchronousQueue。
首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。
这个就拿newFixedThreadPool来说,根据前文提到的规则:
写道
如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
那么当任务继续增加,会发生什么呢?
写道
如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢?
写道
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,如果任务内存大一些,不一会儿就爆了,呵呵。
总结:
- ThreadPoolExecutor的使用还是很有技巧的。
- 使用无界queue可能会耗尽系统资源。
- 使用有界queue可能不能很好的满足性能,需要调节线程数和queue大小
- 线程数自然也有开销,所以需要根据不同应用进行调节。
通常来说对于静态任务可以归为:
- 数量大,但是执行时间很短
- 数量小,但是执行时间较长
- 数量又大执行时间又长
- 除了以上特点外,任务间还有些内在关系
keepAliveTime
jdk中的解释是:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
有点拗口,其实这个不难理解,在使用了“池”的应用中,大多都有类似的参数需要配置。比如数据库连接池,DBCP中的maxIdle,minIdle参数。
总结:
keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。
反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。
RejectedExecutionHandler
在ThreadPoolExecutor中已经默认包含了4中策略:
CallerRunsPolicy: 线程调用运行该任务的 execute 本身。
AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException
DiscardPolicy:不能执行的任务将被删除
DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)
分享到:
相关推荐
ThreadPoolExecutor 是 JDK 自带线程池的核心类,提供了线程池的管理和执行功能。该类提供了多种方法来创建和管理线程池,例如固定大小的线程池、单线程的线程池和计划线程池等。 创建线程池 创建线程池有多种方法...
而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,...
jdk自带的线程池主要是通过ThreadPoolExecutor类来实现的。ThreadPoolExecutor类是Executor框架中最核心的类,它提供了一个灵活的线程池管理机制,允许开发者根据需要创建不同类型的线程池。 二、jdk自带线程池的...
JDK 1.5引入了java.util.concurrent包,其中包含了线程池的实现,使得并发编程更加便捷和高效。线程池的核心在于它的设计策略,包括核心线程数、最大线程数、线程存活时间、工作队列以及拒绝策略。 线程池的主要类...
JDK1[1].5中的线程池(ThreadPoolExecutor)使用简介
JDK1.5中的线程池(ThreadPoolExecutor)使用简介
"JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用" JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用是Java多线程编程中的一种重要概念。随着多线程编程的普及,线程池的使用变得...
JDK线程池可以通过ThreadPoolExecutor类来创建,ThreadPoolExecutor类提供了一个ExecutorService接口的实现。 在使用JDK线程池时,我们可以通过ThreadPoolExecutor类来创建一个线程池,例如: ```java private ...
本篇将探讨如何模拟Java的JDK线程池执行流程,以理解其设计原理。核心知识点包括线程池的执行策略、接口设计以及异常处理。 首先,Java中的线程池设计始于JDK 5.0,主要通过`java.util.concurrent`包中的`Executor`...
corePoolSize: 线程池维护线程的最少数量 maximumPoolSize:线程池维护线程的最大数量 keepAliveTime: 线程池维护线程所允许的空闲时间 unit: 线程池维护线程所允许的空闲时间的单位 workQueue: 线程池所使用的...
《JDK之ThreadPoolExecutor源码分析1》 在Java编程中,线程池是一种高效的管理线程的方式,它通过复用已存在的线程来避免频繁创建和销毁线程带来的开销。ThreadPoolExecutor作为Java中的线程池实现,其内部机制相当...
JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用简介
在Java编程语言中,线程并发和线程池是多任务执行的核心概念,尤其是在JDK 1.5及以后的版本中得到了显著增强。线程并发允许程序同时执行多个任务,提高了程序的效率和响应性。线程池是管理线程资源的有效方式,通过...
我们可以扩展`java.util.concurrent.ThreadPoolExecutor`类,它提供了更复杂的线程池管理功能,包括工作队列、核心线程数、最大线程数等参数。 3. JDK 中的线程池和工作机制 JDK内置了多种线程池实现,如`...
3:对线程池的基本使用及其部分源码的分析(注意:这里的源码分析是基于jdk1.6;) a:线程池的状态 volatile int runState; static final int RUNNING = 0; 运行状态 static final int SHUTDOWN = 1; 关闭状态;...
21.线程池ThreadPoolExecutor实现原理 22.线程池之ScheduledThreadPoolExecutor 23.FutureTask基本操作总结 24.Java中atomic包中的原子操作类总结 25.大白话说java并发工具类-CountDownLatch,CyclicBarrier 26....
在JDK 1.5版本之前,Java对线程池的支持非常有限,而在JDK 1.5之后,加入了java.util.concurrent包,其中包含了一系列关于线程池的接口和类,极大地丰富了线程池的应用场景和管理方式。 线程池的主要作用是限制系统...
但在JDK 1.5及以上版本,推荐使用`ThreadPoolExecutor`,它是线程池的核心实现,提供了更丰富的功能和配置选项。 `ThreadPool`类的构造函数接收一个`poolSize`参数,表示线程池中工作线程的数量,然后创建相应数量...
在早期的JDK版本(如JDK 1.4及之前)中,线程池的功能相对简单,使用起来不够灵活。然而,自JDK 1.5开始,随着`java.util.concurrent`包的引入,Java线程池的功能得到了极大的增强,为开发者提供了更加高效且易于...
从JDK 1.5开始,Java并发API得到了增强,提供了更为强大的并发工具和库,其中就包括线程池的实现。线程池的主要优势在于可以重用线程,减少线程创建和销毁带来的开销,同时还可以有效控制并发数和管理资源。 在给出...