一、背景
在Java线程池学习笔记一中,理解了几个常用的常用的线程池创建的静态工程方法。本篇就Java线程池中的核心:ThreadPoolExecutor,作深入的学习。
二、ThreadPoolExecutor的详细分析
ThreadPoolExecutor是java.util.concurrent包中一个类,它实现了Executor和ExecutorService两个接口,继承了AbstractExecutorService,也是ScheduledThreadPoolExecutor的直接父类。ThreadPoolExecutor的构造函数有以下四个:
1、ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) :用给定的初始参数和默认的线程工厂及被拒绝的执行处理程序创建新的 ThreadPoolExecutor。
2、ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler):用给定的初始参数和默认的线程工厂创建新的 ThreadPoolExecutor。
3、ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory):用给定的初始参数和默认被拒绝的执行处理程序创建新的 ThreadPoolExecutor。
4、ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler):用给定的初始参数创建新的 ThreadPoolExecutor。
这四个构造函数的参数的含义如下:
corePoolSize:表示池中所保存的线程数(包括空闲线程)
maximumPoolSize:池中允许的最大线程数
keepAliveTime:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
unit:keepAliveTime 参数的时间单位
workQueue:执行前用于保持任务的队列,此队列仅保持由 execute方法提交的 Runnable任务
handler:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序
threadFactory:执行程序创建新线程时使用的工厂
在JDK1.7的API中,关于ThreadPoolExecutor的使用有着一样一个说明:强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。
如果需要自己手动配置和调整此类,则可以根据以下说明作为指导:
1、核心和最大池大小
ThreadPoolExecutor 将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见 getMaximumPoolSize())设置的边界自动调整池大小。当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。
2、按需构造
默认情况下,即使核心线程最初只是在新任务到达时才创建和启动的,也可以使用方法 prestartCoreThread() 或 prestartAllCoreThreads() 对其进行动态重写。如果构造带有非空队列的池,则可能希望预先启动线程。
3、创建新线程
使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread 返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。
4、保持活动时间
如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见 getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。默认情况下,保持活动策略只在有多于 corePoolSizeThreads 的线程时应用。但是只要 keepAliveTime 值非 0,allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。
5、排队
所有 BlockingQueue都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
如果运行的线程少于 corePoolSize,则Executor始终首选添加新的线程,而不进行排队。
如果运行的线程等于或多于corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。
排队有三种通用策略:
a.直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
b.无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
c.有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
6、被拒绝的任务
当Executor已经关闭,并且Executor将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下, execute方法都将调用其RejectedExecutionHandler的RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)方法。在API中提供了四种预定义的处理程序的策略:在默认的ThreadPoolExecutor.AbortPolicy中,处理程序遭到拒绝将抛出运行时RejectedExecutionException;在ThreadPoolExecutor.CallerRunsPolicy中,线程调用运行该任务的execute本身(此策略提供简单的反馈控制机制,能够减缓新任务的提交速度);在ThreadPoolExecutor.DiscardPolicy中,不能执行的任务将被删除;在ThreadPoolExecutor.DiscardOldestPolicy中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。
定义和使用其他种类的RejectedExecutionHandler类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。
7、钩子(hook)方法
此类提供protected可重写的beforeExecute(java.lang.Thread, java.lang.Runnable)和afterExecute(java.lang.Runnable, java.lang.Throwable)方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法terminated()来执行Executor完全终止后需要完成的所有特殊处理。如果钩子(hook)或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。
8、队列维护
方法getQueue()允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。remove(java.lang.Runnable)和purge()这两种方法可用于在取消大量已排队任务时帮助进行存储回收。
9、终止
程序不再引用的池没有剩余线程会自动shutdown。如果希望确保回收取消引用的池(即使用户忘记调用shutdown()),则必须安排未使用的线程最终终止:设置适当保持活动时间,使用0核心线程的下边界和/或设置allowCoreThreadTimeOut(boolean)。
三、实例展示
ThreadPoolExecutor可以被很多类进行扩展,下面通过一个实例说明:
import java.util.concurrent.BlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 重写一个或多个受保护的钩子 (hook)方法,实现简单的暂停/恢复功能 */ public class PausableThreadPoolExecutor extends ThreadPoolExecutor { private boolean isPaused; private ReentrantLock pauseLock = new ReentrantLock(); private Condition unpaused = pauseLock.newCondition(); private int corePoolSize ; private int maximumPoolSize ; private long keepAliveTime ; private TimeUnit unit; private BlockingQueue<Runnable> workQueue; private RejectedExecutionHandler handler; public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,handler); this.corePoolSize = corePoolSize ; this.maximumPoolSize = maximumPoolSize; this.keepAliveTime = keepAliveTime ; this.unit = unit ; this.workQueue = workQueue ; this.handler = handler ; } public int getCorePoolSize(){ return corePoolSize ; } public boolean isPaused() { return isPaused; } public ReentrantLock getPauseLock() { return pauseLock; } public Condition getUnpaused() { return unpaused; } public int getMaximumPoolSize() { return maximumPoolSize; } public long getKeepAliveTime() { return keepAliveTime; } public TimeUnit getUnit() { return unit; } public BlockingQueue<Runnable> getWorkQueue() { return workQueue; } public RejectedExecutionHandler getHandler() { return handler; } protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); pauseLock.lock(); try{ while (isPaused) unpaused.await(); }catch(InterruptedException ie) { t.interrupt(); }finally { pauseLock.unlock(); } } public void pause() { pauseLock.lock(); try{ isPaused = true; System.out.println("pause...."); }finally{ pauseLock.unlock(); } } public void resume() { pauseLock.lock(); try{ isPaused = false; unpaused.signalAll(); System.out.println("resume...."); }finally { pauseLock.unlock(); } } }
四、小结
上面的实例只是通过扩展ThreadPoolExecutor类来自己配置线程池的一种方式,在这里强烈建议遵循JDK的说明,毕竟在软件开发这一行业,不需要重复造车。
相关推荐
线程池学习笔记 线程池是 Java 中一个非常重要的概念,它可以帮助我们更好地管理线程,从而提高系统的性能和可维护性。下面是关于线程池的详细知识点。 一、线程池类 Java 中的线程池类是 `java.util.concurrent....
【Java学习笔记Markdown版】是针对Java初学者和进阶者的一份详尽教程,以Markdown格式编写,便于阅读和整理。Markdown是一种轻量级的标记语言,它允许用户使用易读易写的纯文本格式编写文档,然后转换成结构化的HTML...
### Java分布式应用学习笔记07线程池应用 在深入探讨Java分布式应用中线程池的应用之前,我们先来理解一下线程池的基本概念及其在并发编程中的重要性。线程池是Java并发编程的核心技术之一,它通过复用一组预创建的...
当然,现在用过的东西并不是代表以后还能娴熟的使用,做好笔记非常重要; 1:必须明白为什么要使用线程池:(这点很重要) a:手上项目所需,因为项目主要的目的是实现多线程的数据推送;需要创建多线程的话,那...
10. **Java并发编程**:包括线程池、锁机制(如synchronized、ReentrantLock)、并发容器(如ConcurrentHashMap、CopyOnWriteArrayList)以及并发工具类(如CountDownLatch、CyclicBarrier)。 这些是Java基础知识...
Java并发编程实践中的线程池是...总之,理解和掌握Java线程池的原理和配置技巧,能帮助开发者编写出高效、稳定的并发程序,提升系统性能。在实践中,应结合具体业务需求灵活调整线程池参数,以达到最佳的并发执行效果。
"java校招学习笔记"显然是针对应届毕业生或求职者准备的,旨在帮助他们掌握Java的基础知识和校招面试中常见的技术问题。这份笔记可能包含了从基础概念到进阶主题的全面概述,以提高求职者的竞争力。 首先,Java的...
这份"Java学习笔记PPT"是针对Java2版本的学习资源,旨在帮助初学者或有一定基础的开发者深入理解Java的核心概念和应用。 在Java学习的初期,掌握基本语法是至关重要的。Java的语法与C++有诸多相似之处,但更加强调...
这份《Java学习笔记》包含了丰富的知识内容,旨在帮助学习者全面掌握Java编程技术。笔记分为7个PDF部分,覆盖了从基础到高级的Java编程概念。 1. **Java基础知识**:这部分内容通常包括Java的历史背景、环境配置、...
【毕向东Java经典学习笔记Word版】是一份深入讲解Java编程语言的学习资料,由知名IT教育专家毕向东编撰。这份笔记以其系统性、实践性和深度广度深受Java初学者和进阶者的喜爱。毕向东作为Java教育领域的权威人士,他...
本篇学习笔记将深入解析Java线程池的框架、结构、原理以及相关源码,帮助读者全面理解线程池的工作机制。 1. 线程池模块结构 线程池框架分为多层结构,其中包括核心实现类、辅助类和接口等组件。例如,`sun.nio.ch....
2. **面向对象编程**:JAVA是纯面向对象的语言,因此学习笔记中会详细讲解类的创建、继承、封装、多态等概念。同时,接口、抽象类以及访问修饰符也是重要的知识点。 3. **异常处理**:JAVA提供了一种结构化的异常...
Java专题学习笔记主要涵盖了Java语言的核心概念、进阶特性以及实际应用中的问题解析。这份笔记是结合了讲师的讲解和个人的整理,旨在为热爱Java编程的朋友们提供丰富的学习资源。以下将详细介绍其中可能包含的知识点...
Java 6学习笔记是针对初学者和有一定经验的开发者提供的一份全面的教育资源,它涵盖了Java编程语言的基础到高级概念。这份笔记旨在帮助读者深入理解Java 6的关键特性,提高编程技能,并为实际项目开发打下坚实基础。...
《良葛格Java学习笔记(完整版)》是一份全面且深入的Java编程教程,适合不同层次的Java学习者,无论你是初学者还是有经验的开发者,都能从中受益。这份笔记详细介绍了Java语言的核心概念、语法特性以及实际开发中的...
Java 线程学习笔记 Java 线程创建有两种方法: 1. 继承 Thread 类,重写 run 方法:通过继承 Thread 类并重写 run 方法来创建线程,这种方法可以使线程具有自己的执行逻辑。 2. 实现 Runnable 接口:通过实现 ...
Java多线程学习笔记之自定义线程池 本篇文章主要介绍了Java多线程学习笔记之自定义线程池,通过深入了解ThreadPoolExecutor这个核心类,我们可以自定义线程池,满足不同的线程池需求。 Java多线程学习笔记之自定义...
6. **多线程**:Java内置对多线程的支持,学习笔记会讲解Thread类、Runnable接口、同步机制(synchronized关键字、wait/notify)以及线程池的使用。 7. **反射机制**:Java反射允许程序在运行时动态地获取类的信息...
【良葛格Java学习笔记】 本笔记主要涵盖了Java编程语言的核心概念和技术,旨在帮助初学者以及有一定基础的开发者深入理解并掌握Java。Java作为一种广泛应用于企业级应用开发、移动开发(尤其是Android)以及大数据...
这份“非常详细JavaSE学习笔记.rar”压缩包显然是一份全面的Java SE学习资源,包含了从基础知识到高级特性的全方位讲解。下面,我们将详细探讨这份笔记可能涵盖的关键知识点。 1. **Java起源与环境搭建**:笔记可能...