网站优化成都哪里好,南阳做做网站,yy直播下载免费下载,dw做网站怎么连接gif图片大家好#xff0c;我是栗筝i#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 024 篇文章#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验#xff0c;并希望进… 大家好我是栗筝i这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 024 篇文章在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时本专栏的所有文章也都会准备充足的代码示例和完善的知识点梳理因此也十分适合零基础的小白和要准备工作面试的同学学习。当然我也会在必要的时候进行相关技术深度的技术解读相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。 – 在现代软件开发中随着计算资源的增多和应用需求的复杂化如何高效地管理并发任务成为一个关键问题。无论是处理海量数据的后台服务还是需要实时响应的用户界面应用都离不开多线程编程。Java 作为一种广泛使用的编程语言提供了丰富的并发工具其中线程池是实现高效并发的核心组件之一。 线程池通过重用现有的线程减少了频繁创建和销毁线程的开销同时能够有效地控制并发任务的数量防止系统过载。借助线程池开发者可以更方便地管理和调度任务提高系统的响应速度和资源利用率。然而正确理解和使用线程池并非易事合理配置线程池参数、选择适当的拒绝策略、避免常见陷阱都是确保系统稳定运行的关键。 本文将深入介绍 Java 线程池的基本概念、常用类型及其适用场景并结合实际代码示例展示如何在不同场景下合理使用线程池。希望通过本篇文章读者能对 Java 线程池有一个全面的了解并能在实际开发中应用自如从而编写出更高效、更稳定的并发程序。 文章目录 1、Java 线程池介绍2、Java 线程池的执行流程2.1、线程池的几个重要参数2.2、线程池的执行流程2.3、拒绝策略2.3.1、AbortPolicy2.3.2、CallerRunsPolicy2.3.3、DiscardPolicy2.3.4、DiscardOldestPolicy2.3.5、自定义拒绝策略 2.4、线程池状态 3、Java 线程池的使用3.1、常用的线程池3.2、Executor 框架3.3、ThreadPoolExecutor创建线程池3.4、Executor 框架的继承关系 1、Java 线程池介绍
池化技术现在已经屡见不鲜了线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗提高对资源的利用率。
线程池Thread Pool是一种基于池化思想管理线程的工具由于创建和关闭线程需要花费时间如果为每一个任务都创建一个线程非常消耗资源。使用线程池可以避免增加创建和销毁线程的资源消耗提高响应速度且能重复利用线程。在使用线程池后创建线程就变成了从线程池中获取空闲线程关闭线程变成了向线程池归还线程。
线程池做的工作主要是控制运行的线程的数量处理过程中将任务放入队列然后在线程创建后启动这些任务使用完毕不需要销毁线程而是放回池中如果线程数量超过了最大数量超出数量的线程排队等候等其它线程执行完毕再从队列中取出任务来执行。他的主要特点为线程复用、控制最大并发数、管理线程。
使用线程池的好处
降低资源消耗通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度当任务到达时任务可以不需要的等到线程创建就能立即执行。提高线程的可管理性线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。 2、Java 线程池的执行流程
2.1、线程池的几个重要参数
线程池通过复用线程来提高性能并减少线程创建和销毁的开销。
public class ThreadPoolExecutor extends AbstractExecutorService {// 省略其他方法和实现细节.../*** 创建一个新的 {code ThreadPoolExecutor} 实例使用给定的初始参数。** param corePoolSize 保持在池中的线程数量即使它们是空闲的除非设置了 {code allowCoreThreadTimeOut}* param maximumPoolSize 允许在池中存在的最大线程数量* param keepAliveTime 当线程数量大于核心线程数时多余的空闲线程在终止之前等待新任务的最长时间* param unit {code keepAliveTime} 参数的时间单位* param workQueue 用于在任务执行前保存任务的队列。这个队列只会保存通过 {code execute} 方法提交的 {code Runnable} 任务* param threadFactory 当执行器创建新线程时使用的工厂* param handler 当执行由于线程边界和队列容量达到限制而被阻塞时使用的处理程序* throws IllegalArgumentException 如果以下任一情况成立br* {code corePoolSize 0}br* {code keepAliveTime 0}br* {code maximumPoolSize 0}br* {code maximumPoolSize corePoolSize}* throws NullPointerException 如果 {code workQueue} 或 {code threadFactory} 或 {code handler} 为 null*/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {// 检查传入参数的合法性if (corePoolSize 0 ||maximumPoolSize 0 ||maximumPoolSize corePoolSize ||keepAliveTime 0)throw new IllegalArgumentException();if (workQueue null || threadFactory null || handler null)throw new NullPointerException();// 初始化参数this.corePoolSize corePoolSize;this.maximumPoolSize maximumPoolSize;this.workQueue workQueue;this.keepAliveTime unit.toNanos(keepAliveTime); // 转换 keepAliveTime 为纳秒this.threadFactory threadFactory;this.handler handler;// 创建线程池容器String name Objects.toIdentityString(this);this.container SharedThreadContainer.create(name);}// 省略其他方法和实现细节...}在配置线程池时有几个重要参数需要理解和配置 核心线程数corePoolSize这是线程池中始终保持存活的线程数量。即使这些线程处于空闲状态线程池也不会销毁它们核心线程数决定了线程池在接收到任务时初始的并发处理能力 最大线程数maximumPoolSize这是线程池中允许的最大线程数量。当任务队列已满且核心线程都在忙碌时线程池会创建新线程直到达到最大线程数。这个参数决定了线程池能够处理的最大并发任务数量 空闲线程存活时间keepAliveTime当线程池中的线程数超过核心线程数且这些超出核心线程数的线程空闲时间超过了 keepAliveTime这些线程会被终止并从池中移除这个参数有助于在任务负载减小时减少资源消耗 时间单位unit这是 keepAliveTime 参数的时间单位如秒TimeUnit.SECONDS、毫秒TimeUnit.MILLISECONDS等 任务队列workQueue用于存放等待执行的任务。常见的任务队列有 LinkedBlockingQueue、SynchronousQueue 和 ArrayBlockingQueue 等任务队列的选择会影响线程池的行为和性能。例如SynchronousQueue 不存储任务而是直接将任务交给工作线程处理 线程工厂threadFactory用于创建新线程。可以自定义线程工厂来设置线程的名称、优先级等。例如可以使用 Executors.defaultThreadFactory() 来获得默认的线程工厂 拒绝策略RejectedExecutionHandler当任务队列已满且线程池中的线程数量已达到最大线程数时线程池会执行拒绝策略。 常见的拒绝策略包括 AbortPolicy抛出 RejectedExecutionException 异常。CallerRunsPolicy由调用线程执行任务。DiscardPolicy直接丢弃任务不抛出异常。DiscardOldestPolicy丢弃队列中最旧的任务然后尝试提交新的任务。
配置合理的线程池参数可以有效地管理资源提高系统的并发处理能力并确保系统的稳定性和响应速度。在实际应用中需要根据具体的需求和工作负载进行调优。
2.2、线程池的执行流程
线程池ThreadPoolExecutor的执行流程如下
public class ThreadPoolExecutor extends AbstractExecutorService {// 省略其他方法和实现细节.../*** 在将来的某个时间执行给定的任务。任务可能在一个新的线程中执行也可能在一个已有的线程池线程中执行。* p* 如果任务不能被提交执行无论是因为这个执行器已经关闭还是因为它的容量已经达到了极限任务都将由当前的* {link RejectedExecutionHandler} 处理。** param command 要执行的任务* throws RejectedExecutionException 在 {code RejectedExecutionHandler} 的判断下如果任务不能被接受执行* throws NullPointerException 如果 {code command} 为 null*/public void execute(Runnable command) {if (command null)throw new NullPointerException();/** 分三步进行** 1. 如果运行的线程少于 corePoolSize尝试启动一个新的线程并将给定的命令作为其第一个任务。* 对 addWorker 的调用会原子地检查运行状态和工作线程数防止在不应添加线程时误报警。** 2. 如果任务能够成功入队那么我们仍然需要再次检查是否应该添加一个线程因为现有的线程在上次检查后可能已经终止* 或者线程池自进入此方法以来是否已关闭。因此我们重新检查状态如果必要的话在停止时回滚入队操作* 或者如果没有线程则启动一个新的线程。** 3. 如果我们不能将任务入队那么我们尝试添加一个新的线程。如果失败我们知道要么是已经关闭要么是已饱和* 因此拒绝任务。*/// 获取线程池的状态和工作线程数int c ctl.get();// 如果工作线程数小于核心线程数if (workerCountOf(c) corePoolSize) {// 尝试添加一个新的工作线程来执行任务if (addWorker(command, true))return;c ctl.get();}// 如果线程池在运行且任务成功入队if (isRunning(c) workQueue.offer(command)) {int recheck ctl.get();// 如果线程池不再运行且任务被成功移除if (!isRunning(recheck) remove(command))// 拒绝任务reject(command);// 如果没有工作线程else if (workerCountOf(recheck) 0)// 添加一个新的工作线程addWorker(null, false);// 如果不能将任务入队且添加新线程失败} else if (!addWorker(command, false))// 拒绝任务reject(command);}// 省略其他方法和实现细节...}以下是对这个流程的详细步骤介绍 任务提交通过 execute(Runnable) 方法或 submit(CallableT) 方法向线程池提交任务。 线程处理任务线程池首先判断核心线程池中的线程数是否已达到 corePoolSize。 如果没有达到创建一个新的线程来处理任务如果达到了将任务放入工作队列 workQueue 任务队列 如果工作队列未满任务被添加到队列中等待执行。如果工作队列已满且线程池中的线程数小于 maximumPoolSize则创建新的线程来处理任务。如果工作队列已满且线程池中的线程数已达到 maximumPoolSize则执行拒绝策略RejectedExecutionHandler。 线程执行任务 线程从任务队列中取出任务并执行当线程完成任务后会继续从队列中取出下一个任务执行。 线程存活时间 当线程池中的线程数超过核心线程数并且这些多余的线程空闲时间超过 keepAliveTime则这些线程会被终止并从池中移除。这样可以避免线程池在任务负载较低时占用过多资源。
图示化线程池执行流程 任务提交|是否可以创建核心线程/ \
是 否
/ \
创建核心线程 将任务放入工作队列|是否队列已满/ \否 是/ \放入队列 是否可以创建新线程/ \是 否/ \创建新线程 执行拒绝策略2.3、拒绝策略
线程池的拒绝策略RejectedExecutionHandler用于处理当线程池无法执行新的任务时的情况。主要有四种内置的拒绝策略分别是
2.3.1、AbortPolicy
这是默认的拒绝策略。当线程池无法处理新的任务时它将抛出 RejectedExecutionException。使用场景当你希望在任务无法被执行时立即得到通知并采取相应的措施。
示例代码
RejectedExecutionHandler handler new ThreadPoolExecutor.AbortPolicy();2.3.2、CallerRunsPolicy
当线程池无法处理新的任务时该策略会让提交任务的线程直接运行这个任务。使用场景当你希望减缓新任务的提交速度时该策略可以让调用线程参与执行任务从而降低任务提交的速率。
示例代码
RejectedExecutionHandler handler new ThreadPoolExecutor.CallerRunsPolicy();2.3.3、DiscardPolicy
当线程池无法处理新的任务时该策略会直接丢弃被拒绝的任务不做任何处理也不抛出异常。使用场景当你可以接受某些任务被静默丢弃且不需要额外的处理时。
示例代码
RejectedExecutionHandler handler new ThreadPoolExecutor.DiscardPolicy();2.3.4、DiscardOldestPolicy
当线程池无法处理新的任务时该策略会丢弃队列中最旧的任务然后重新尝试提交被拒绝的任务。使用场景当你希望优先处理最新的任务可以接受丢弃一些旧任务时。
示例代码
RejectedExecutionHandler handler new ThreadPoolExecutor.DiscardOldestPolicy();2.3.5、自定义拒绝策略
除了上述四种内置策略外你还可以实现 RejectedExecutionHandler 接口来创建自定义的拒绝策略。例如
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 自定义拒绝处理逻辑例如记录日志或将任务放入另一个队列中System.out.println(Task r.toString() rejected from executor.toString());}
}2.4、线程池状态
Java 线程池ThreadPoolExecutor的状态主要通过一个称为 ctl 的原子变量来表示这个变量同时包含了线程池的运行状态和线程池中的有效线程数量。ctl 变量是一个 AtomicInteger 类型的变量其高3位表示线程池的状态低29位表示线程池中的线程数量。
线程池的状态主要有以下几种
RUNNING线程池在正常运行状态可以接受新的任务并处理队列中的任务。SHUTDOWN调用了 shutdown() 方法后线程池进入该状态不再接受新任务但会继续处理队列中的任务。STOP调用了 shutdownNow() 方法后线程池进入该状态不再接受新任务并且会中断正在处理的任务和清空队列中的任务。TIDYING所有任务都已终止工作线程数为零线程池将要调用 terminated() 方法。TERMINATEDterminated() 方法已经完成执行线程池完全终止。
ctl 变量的高3位表示线程池状态低29位表示工作线程数量。以下是几个常量的定义
private static final int COUNT_BITS Integer.SIZE - 3;
private static final int CAPACITY (1 COUNT_BITS) - 1;private static final int RUNNING -1 COUNT_BITS;
private static final int SHUTDOWN 0 COUNT_BITS;
private static final int STOP 1 COUNT_BITS;
private static final int TIDYING 2 COUNT_BITS;
private static final int TERMINATED 3 COUNT_BITS;通过这些常量的定义可以看出线程池状态的高 3 位是如何设置的。
线程池在不同状态之间的转换流程如下
RUNNING - SHUTDOWN调用 shutdown() 方法后线程池状态从 RUNNING 转变为 SHUTDOWN线程池不再接受新任务但会继续处理已提交的任务。(RUNNING or SHUTDOWN) - STOP调用 shutdownNow() 方法后线程池状态从 RUNNING 或 SHUTDOWN 转变为 STOP线程池不再接受新任务并中断所有正在执行的任务。SHUTDOWN - TIDYING当所有任务都已完成线程池状态从 SHUTDOWN 转变为 TIDYING。STOP - TIDYING当所有任务都已中断线程池状态从 STOP 转变为 TIDYING。TIDYING - TERMINATEDterminated() 方法执行完毕后线程池状态从 TIDYING 转变为 TERMINATED。
以下是线程池状态转换的示例代码
public void shutdown() {final ReentrantLock mainLock this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(SHUTDOWN);interruptIdleWorkers();onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}tryTerminate();
}public ListRunnable shutdownNow() {final ReentrantLock mainLock this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(STOP);interruptWorkers();taskQueue.clear();} finally {mainLock.unlock();}return super.shutdownNow();
}在 shutdown() 方法中通过 advanceRunState(SHUTDOWN) 将状态转变为 SHUTDOWN在 shutdownNow() 方法中通过 advanceRunState(STOP) 将状态转变为 STOP。 3、Java 线程池的使用
3.1、常用的线程池
在 Java 中Executors 提供了一些便捷的方法来创建常用的线程池但在实际开发中为了避免潜在的问题和更好地控制线程池的行为通常建议直接使用 ThreadPoolExecutor 来创建线程池。下面是对一些常用线程池的介绍和它们可能存在的问题
newSingleThreadExecutor 创建一个单线程的线程池只有一个线程在工作所有任务按照提交顺序执行。如果唯一的线程因为异常终止会有一个新线程替代它继续工作。使用场景适用于需要确保顺序执行任务的场景。
代码示例
ExecutorService executor Executors.newSingleThreadExecutor();newFixedThreadPool 描述创建一个固定大小的线程池。每次提交一个任务就创建一个线程直到达到线程池的最大大小。线程池大小达到最大值后将继续保持固定大小如果某个线程因为异常终止会补充一个新线程。使用场景适用于需要限制并发线程数控制资源使用的场景。
代码示例
ExecutorService executor Executors.newFixedThreadPool(10);newCachedThreadPool 描述创建一个可缓存的线程池。如果线程池中有空闲线程可以重用则会重用空闲线程如果没有空闲线程则创建新线程。空闲线程会在60秒没有任务执行时被终止并移除。使用场景适用于执行许多短期异步任务的小程序或者负载较轻的服务器。
代码示例
ExecutorService executor Executors.newCachedThreadPool();newScheduledThreadPool 描述创建一个支持定时和周期性任务执行的线程池。可以用于需要定时执行任务的场景。使用场景适用于需要定时或者周期性执行任务的场景。
代码示例
ScheduledExecutorService executor Executors.newScheduledThreadPool(5);使用 Executors 的弊端阿里巴巴 Java 开发手册建议不要使用 Executors 创建线程池而是直接使用 ThreadPoolExecutor主要是为了避免以下问题 newFixedThreadPool 和 newSingleThreadExecutor问题使用无界的请求队列 LinkedBlockingQueue可能会导致请求堆积耗尽内存甚至导致 OutOfMemoryError。 newCachedThreadPool 和 newScheduledThreadPool问题允许创建的线程数最大值为 Integer.MAX_VALUE可能会创建大量线程耗尽系统资源甚至导致 OutOfMemoryError。
为了更好地控制线程池的行为建议使用 ThreadPoolExecutor 直接创建线程池。这样可以更明确地控制线程池的核心参数如核心线程数、最大线程数、空闲线程存活时间、任务队列等。
3.2、Executor 框架
Java 中的线程池是通过 Executor 框架实现的该框架中用到了 ExecutorExecutorsExecutorServiceThreadPoolExecutor Callable 和 Future、FutureTask 这几个类
Executor 框架是 Java5 之后引进的在 Java 5 之后通过 Executor 来启动线程比使用 Thread 的 start 方法更好除了更易管理效率更好用线程池实现节约开销外还有关键的一点有助于避免 this 逃逸问题。
this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。
Executor 框架不仅包括了线程池的管理还提供了线程工厂、队列以及拒绝策略等Executor 框架让并发编程变得更加简单。
3.3、ThreadPoolExecutor创建线程池
ThreadPoolExecutor 是线程池的核心实现。线程的创建和终止需要很大的开销线程池中预先提供了指定数量的可重用线程所以使用线程池会节省系统资源并且每个线程池都维护了一些基础的数据统计方便线程的管理和监控。
通过下面的demo来了解ThreadPoolExecutor创建线程的过程。
public class TestThreadPool {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new LinkedBlockingDeque(5));ExecutorCompletionServiceString executorCompletionService new ExecutorCompletionService(threadPoolExecutor);for (int i 0; i 20; i) {try {executorCompletionService.submit(()- { try { //System.out.println(---); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } },testtaski);System.out.print( New task: testtask i);System.out.print( ActiveCount: threadPoolExecutor.getActiveCount());System.out.print( poolSize: threadPoolExecutor.getPoolSize());System.out.print( queueSize: threadPoolExecutor.getQueue().size());System.out.println( taskCount: threadPoolExecutor.getTaskCount());} catch (RejectedExecutionException e) {System.out.println(Reject i);}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}threadPoolExecutor.shutdown();}
}3.4、Executor 框架的继承关系
Java 中的线程池核心实现类是 ThreadPoolExecutor先通过 JDK 1.8 中 ThreadPoolExecutor 的 UML 类图了解下 ThreadPoolExecutor 的继承关系。 ThreadPoolExecutor 实现的顶层接口是 Executor顶层接口 Executor 提供了一种思想将任务提交和任务执行进行解耦。用户无需关注如何创建线程如何调度线程来执行任务用户只需提供 Runnable 对象将任务的运行逻辑提交到执行器Executor中由 Executor 框架完成线程的调配和任务的执行部分。ExecutorService 接口增加了一些能力
扩充执行任务的能力补充可以为一个或一批异步任务生成 Future 的方法提供了管控线程池的方法比如停止线程池的运行。
AbstractExecutorService 则是上层的抽象类将执行任务的流程串联了起来保证下层的实现只需关注一个执行任务的方法即可。最下层的实现类 ThreadPoolExecutor 实现最复杂的运行部分ThreadPoolExecutor 将会一方面维护自身的生命周期另一方面同时管理线程和任务使两者良好的结合从而执行并行任务。