目录
- 综述
- 七个核心参数
- 线程工厂
- 拒绝策略
- AbortPolicy
- CallerRunsPolicy
- DiscardOldestPolicy
- DiscardPolicy
- 自定义拒绝策略
- 监控线程池运行状态
- 关闭和动态调整线程池|done
- 线程池的缺点
- 提交任务
- 结束语
- Reference
摘要:分享JAVA JUC线程池干货,首先描述线程池的基本概念,然后介绍线程工厂和拒绝策略,其次逐步深入线程池实现原理和线程池状态机,最后结合实战讲解源码。
JUC干货系列目录:
- JAVA JUC干货之线程池状态和状态切换
- JAVA JUC干货之线程池实现原理和源码详解(上)
- JAVA JUC干货之线程池实现原理和源码详解(下)
综述
世界唯一不变的事,就是世界一直在变,而且是瞬息万变,唯有不断学习、持续创新和迎接变化,才能立于不败之地。一位金融公司的CTO曾经问我“为什么使用线程池?线程池是怎样执行任务的?”我由于对这个知识点掌握的不透彻,只能临场发挥,导致结局尴尬。因此亡羊补牢,当天到家后就梳理了这个知识点,现在结合Java 21线程池源码落地到文档,分享一些关于线程池的干货,包括但不限于基本概念、执行流程、使用方法、最佳实践和大厂八股文。
我们下面认识一下什么是线程池。线程池从字面意思上来看就是一个基于池化技术管理同一组工作线程的池子,基本概念如下:是一种用于管理线程生命周期和任务执行的工具,它通过复用已有的工作线程,避免频繁创建和销毁工作线程的开销,从而显著提高应用程序的响应速度和吞吐量,提升资源利用率。通常,我们会使用 java.util.concurrent.ThreadPoolExecutor 或者 Spring 提供的 ThreadPoolExecutor 来创建和管理线程池。
Java在使用线程执行程序时,需要调用操作系统内核的API创建一个内核线程,操作系统要为线程分配一系列的系统资源;当该Java线程被终止时,对应的内核线程也会被回收。因此,频繁的创建和销毁线程需要消耗大量资源。此外,由于CPU核数有限,大量的线程上下文切换会增加系统的性能开销,无限制地创建线程还可能导致内存溢出。为此,Java在JDK1.5版本中引入了线程池。
在项目开发过程中为什么使用线程池?我们先看看的ThreadPoolExecutor类中英文注释是怎么描述的:- Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks,
- due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources,
- including threads, consumed when executing a collection of tasks. Each {@code ThreadPoolExecutor} also maintains some basic statistics,
- such as the number of completed tasks.
复制代码 中文意思大致就是线程池解决了两个不同的问题:在执行大量异步任务时,它们一般通过复用线程降低每个任务的调用开销来提高性能,同时线程池还提供了一种限制和管理执行任务时所消耗资源(包括线程)的方法。每个 {@code ThreadPoolExecutor} 还会维护一些基本的统计信息,例如已完成的任务数量。简而言之,线程池能够对线程进行统一分配、调优和监控:
- 通过线程复用机制降低资源开销。线程池维护了一个线程集合,尽可能复用线程完成不同的任务,避免了反反复复地创建和销毁线程带来的系统资源开销。
- 更有效的管理系统资源。使用线程池统一管理和监控系统资源,做到根据系统承载能力限制同时运行的线程数量,防止系统因创建过多线程而耗尽资源。还支持动态调整核心线程数。
- 提高响应速度。由于线程被提前预热,当有任务到达时立即被执行,因此显著减少了创建线程这段时间的开销,从而提高了系统的响应速度。据统计,创建一个线程大约耗时90微秒并占用1M内存。
鉴于以上线程池优势,合理使用线程池可以帮助我们构建更加高效、稳定和易于维护的多线程应用程序。在业务系统开发过程中,线程池的两个常见应用场景分别是快速响应用户请求和高效处理批量任务。本文将全方位深入探讨 Java 线程池,帮助读者掌握线程池使用技巧和精通其原理。线程池源码参考Java 21,具体版本是“21.0.6”。
七个核心参数
什么是任务执行器?它是实际执行任务的组件,包括执行任务的核心接口类 Executor和继承了 Executor 的 ExecutorService 接口。Executor 框架有几个关键类实现了 ExecutorService 接口:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor、ForkJoinPool。Executor定义了一个简单的 execute(Runnable command) 方法用于异步执行任务;而ExecutorService 接口继承自 Executor,添加了更丰富的任务提交和生命周期管理轮子,如 submit(Runnable task)、submit(Callable task) 、isTerminated()、shutdown() 等。
楼兰胡杨在分析JUC线程池Executor框架体系时发现线程池的核心实现类是ThreadPoolExecutor,它是 Executor 框架中最重要的任务执行器实现,实现了 ExecutorService 接口。提供了一个可配置的线程池,用于执行异步任务。
正所谓“先穿袜子后穿鞋,先当孙子后当爷”,如果要深入理解Java并发编程中的线程池,那么必须深入理解这个类的构造函数。我们来看一下ThreadPoolExecutor类中四个构造函数的源码:
[code]public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);}public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);}public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);}public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize |