一心多用多线程-线程池ThreadPoolExecutor-看这篇就够了
许久之前理解了java线程池ThreadPoolExecutor,今天来做一个总结,根据java api加上自己的理解,让我们能更透彻的理解java线程池
首先先写一下线程池的概念:
线程池:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
在java里面,我们就是使用ThreadPoolExecutor来创建一个线程池,首先我们先来看看ThreadPoolExecutor的构造函数,通过构造函数的参数来认识一下如何构造一个线程池
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)
用给定的初始参数和默认的线程工厂及被拒绝的执行处理程序创建新的 ThreadPoolExecutor。ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long
keepAliveTime, TimeUnit unit, BlockingQueue workQueue,
RejectedExecutionHandler handler)
用给定的初始参数和默认的线程工厂创建新的 ThreadPoolExecutor。ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long
keepAliveTime, TimeUnit unit, BlockingQueue workQueue,
ThreadFactory threadFactory)
用给定的初始参数和默认被拒绝的执行处理程序创建新的 ThreadPoolExecutor。ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long
keepAliveTime, TimeUnit unit, BlockingQueue workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler)
用给定的初始参数创建新的 ThreadPoolExecutor。
其实前面三个构造函数在底层都是使用第四个构造方法,只是在调用的过程中添加了一些默认的条件而已,好,那现在我们根据第四个构造函数的参数来认识线程池
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,在这种情况下,任务将被拒绝(详细可以查看第6点)。
排队有三种通用策略:
- 直接提交。工作队列的默认选项是SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
- 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
解决问题:任务到了线程池后存储在什么地方
6.被拒绝的任务
当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。
下面提供了四种预定义的处理程序策略:
- 在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时
RejectedExecutionException。 - 在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute
本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。 - 在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。 在
ThreadPoolExecutor.DiscardOldestPolicy
中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。
static class ThreadPoolExecutor.AbortPolicy 用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException.
static class ThreadPoolExecutor.CallerRunsPolicy 用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。
static class ThreadPoolExecutor.DiscardOldestPolicy 用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。
static class ThreadPoolExecutor.DiscardPolicy 用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
定义和使用其他种类的 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.终止
程序 AND 不再引用的池没有剩余线程会自动 shutdown。如果希望确保回收取消引用的池(即使用户忘记调用 shutdown()),则必须安排未使用的线程最终终止:设置适当保持活动时间,使用 0 核心线程的下边界和/或设置 allowCoreThreadTimeOut(boolean)。
对线程池的补充:
在java中我们可以使用Executors类直接创建一个已配置好线程池,Executors内部也是调用ThreadPoolExecutor去创建线程池的,详细见api
一心多用多线程-线程池ThreadPoolExecutor-看这篇就够了相关推荐
- 字符串常量池,看这篇就够了(二)
哈喽,我是子牙.十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业.技术栈如汇编.C语言.C++.Windows内核.Linux内核.特别喜欢研究虚拟机底层实现,对JVM有深入研究.分 ...
- 字符串常量池,看这篇就够了(一)
哈喽,我是子牙.十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业.技术栈如汇编.C语言.C++.Windows内核.Linux内核.特别喜欢研究虚拟机底层实现,对JVM有深入研究.分 ...
- 一心多用多线程-线程的生命周期
想起前段时间做校招的时候经常会被问到线程的生命周期问题,现在结合api做一下归纳 1.新建状态 new Thread() 创建一个线程,现在的线程并没有具备运动能力的,可以想象你新建了一只兔子,但是这 ...
- 一心多用多线程-线程创建的三种方式
第一次了解java线程机制,记录一下线程启动的三种方式. 1.通过继承Thread类调用一个线程 public class Thread1 extends Thread{@Overridepublic ...
- Java多线程- 线程池的基本使用和执行流程分析 - ThreadPoolExecutor
线程池的实现原理 池化技术 一说到线程池自然就会想到池化技术. 其实所谓池化技术,就是把一些能够复用的东西放到池中,避免重复创建.销毁的开销,从而极大提高性能. 常见池化技术的例如: 线程池 内存池 ...
- c++ 线程池_JAVA并发编程:线程池ThreadPoolExecutor源码分析
前面的文章已经详细分析了线程池的工作原理及其基本应用,接下来本文将从底层源码分析一下线程池的执行过程.在看源码的时候,首先带着以下两个问题去仔细阅读.一是线程池如何保证核心线程数不会被销毁,空闲线程数 ...
- 并发编程(七)好用的线程池ThreadPoolExecutor
并发编程专栏系列博客 并发编程(一)python并发编程简介 并发编程(二)怎样选择多线程多进程和多协程 并发编程(三)Python编程慢的罪魁祸首.全局解释器锁GIL 并发编程(四)如何使用多线程, ...
- 多线程线程池的实现java_如何在Java中实现线程池
多线程线程池的实现java 线程是独立程序的执行路径. 在java中,每个线程都扩展java.lang.Thread类或实现java.lang.Runnable. 多线程是指在一个任务中同时执行两个或 ...
- 多线程线程池的基本创建,使用方法
import java.util.concurrent.*;/*** 多线程线程池的基本创建,使用方法** @author silence*/ public class Silence {public ...
最新文章
- 1.sql 数据据基础_数据库的组成
- Oracle 在 多个Virtualbox 虚拟机间 跨不同物理宿主机进行通信
- K8S精华问答 | 应用和运行时平台是怎样解耦的?
- 台式计算机总是重启,台式电脑经常自动重启怎么修复
- python的random模块怎么写_Python常用标准库之random模块
- outset边框html,CSS3 border-image-outset属性怎么用?
- Microsoft Updater Application Block 1.2.1 核心设计(core design) [翻译]
- 五大科技巨头公司 是如何赚取数十亿美元的
- npm 安装axios报错
- 三津谈保险系统建设(一): 现状分析和建设目标规划
- 软件商业模式的发展与2.0时代
- Share:思科模拟器 路由配置(默认、静态、动态RIP)
- uniapp微信授权登录
- Python中filter筛选函数匿名参数问题
- 新手小白如何挑选吉他,附几款超高性价比吉他推荐
- 如何一次性下载全国谷歌卫星影像地图数据
- android自动夜间模式吗,Android夜间模式的实现方案
- 使用Jekyll + GitHub 搭建自己的博客
- 使用Arduino开发板进行语音识别
- linux centos7 镜像下载