在现代多核处理器时代,线程池成为了并发编程中不可或缺的工具,它不仅提高了程序性能,还简化了线程管理。线程池允许我们重用有限数量的线程来执行大量任务,从而减少了线程创建和销毁的开销。Java中的ExecutorService
接口及其实现类,如FixedThreadPool
、SingleThreadExecutor
、CachedThreadPool
和ScheduledThreadPool
,提供了强大的线程池管理功能。这些线程池通过智能地调度任务和复用线程,帮助我们优化资源利用,提高响应速度,并处理复杂的并发场景。对于Java开发者而言,理解线程池的工作原理和正确选择适当的线程池类型对于构建高效、可伸缩的并发应用至关重要。
肖哥弹架构 跟大家“弹弹” 高并发锁, 关注公号回复 ‘mvcc’ 获得手写数据库事务代码
欢迎 点赞,关注,评论。
关注公号Solomon肖哥弹架构获取更多精彩内容
历史热点文章
- 高并发编程/一张图精通CompletableFuture整体执行流程与设计(高手篇)
- 高并发编程/并行任务组件ForkJoinPool设计图分解(高手篇)
- 精通Java并发锁机制:24种锁技巧+业务锁匹配方案(第一部分)
- 并发处理的利器:深入探讨锁分离设计案例(高并发篇)
- 高并发设计之细粒度锁 :5种细粒度锁的设计技巧图解(高并发篇)
- 17个Mybatis Plugs注解:Mybatis Plugs插件架构设计与注解案例(必须收藏)
1、线程池工作流程
- ExecutorService:这是线程池的管理接口,负责提交任务和管理工作线程。
- 任务队列(Task Queue) :这是一个先进先出(FIFO)的队列,用于存储待执行的任务。
- 线程池(Thread Pool) :这是一组工作线程的集合,它们从任务队列中取出任务并执行。
- 工作线程(Worker Thread) :线程池中的每个线程都会循环地从任务队列中取出任务并执行。
- 任务(Task) :这是需要执行的具体任务,可以是
Runnable
或Callable
对象。 - 返回结果(Return Result) :任务执行完成后,会返回结果或异常信息。
2、ExecutorService
设计本质
- 线程生命周期管理:
- 在Java 5之前,开发者需要手动管理线程的创建和销毁,这不仅增加了编程复杂性,还可能导致资源浪费和系统开销。
ExecutorService
通过提供线程池管理功能,简化了线程的生命周期管理。
- 在Java 5之前,开发者需要手动管理线程的创建和销毁,这不仅增加了编程复杂性,还可能导致资源浪费和系统开销。
- 系统开销降低:
- 频繁地创建和销毁线程会导致性能问题和资源消耗。
ExecutorService
允许线程池重用线程,从而降低了系统开销。
- 频繁地创建和销毁线程会导致性能问题和资源消耗。
- 资源利用率提升:
- 通过线程池复用线程,
ExecutorService
提高了资源利用率和程序响应速度,使得多线程编程更加灵活和高效。
- 通过线程池复用线程,
- 丰富的任务调度和并发控制:
ExecutorService
提供了丰富的任务调度和并发控制能力,使得多线程编程更加灵活和高效。
- 硬件发展推动:
- 随着多核架构的出现,Java的设计者们决定重新修订Java的内存模型,并在JDK1.5中引入了
java.util.concurrent
包,其中就包括了ExecutorService
接口,以支持更高效的并行计算。
- 随着多核架构的出现,Java的设计者们决定重新修订Java的内存模型,并在JDK1.5中引入了
- 简化并发编程:
ExecutorService
作为Java并发编程的重要工具,简化了并发编程的复杂性,使得开发者可以更容易地实现并行处理和任务调度。
- 提高程序性能:
ExecutorService
通过减少线程创建和销毁的开销,提高了程序的性能和可伸缩性。
- 线程池的易用性:
Executors
类提供了便捷的工厂方法来创建不同类型的线程池,使得开发者无需手动实现复杂的线程池逻辑,就可以方便地使用线程池。
3、线程池类设计
在这个类设计图中,我们有以下组件:
- ExecutorService:这是一个接口,定义了线程池管理的方法,如
submit
、invokeAll
、invokeAny
、shutdown
等。 - ThreadPoolExecutor:这是
ExecutorService
的一个具体实现,提供了线程池的详细控制,如execute
、submit
、shutdown
等。 - ScheduledExecutorService:这是
ExecutorService
的一个子接口,用于延迟执行或定期执行任务。 - FutureTask:这是
Future
接口的一个实现类,用于封装异步任务,并提供方法如run
、get
、isDone
等。
4、线程池功能范围设计
4.1. 接口定义
ExecutorService
扩展了 Executor
接口,增加了提交任务后返回 Future
对象的方法,这些方法允许任务异步执行,并提供了获取任务结果的机制。
4.2. 任务提交
submit(Callable<T> task)
: 提交一个返回结果的任务,并返回一个Future
对象。submit(Runnable task)
: 提交一个不返回结果的任务,并返回一个Future
对象。submit(Runnable task, T result)
: 提交一个不返回结果的任务,并返回一个已经设置好结果的Future
对象。
4.3. 批量任务执行
invokeAll(Collection<? extends Callable<T>> tasks)
: 提交一个任务集合,等待所有任务完成,并返回每个任务结果的列表。invokeAny(Collection<? extends Callable<T>> tasks)
: 提交一个任务集合,等待任意一个任务完成,并返回该任务的结果。
4.4. 线程池管理
shutdown()
: 启动一次有序的关闭,执行已提交的任务,不接受新任务。shutdownNow()
: 尝试停止所有正在执行的任务,并返回未执行任务的列表。awaitTermination(long timeout, TimeUnit unit)
: 等待直到所有任务完成或超时。
4.5. 线程生命周期
ExecutorService
允许线程的复用,减少了线程创建和销毁的开销。线程池可以根据需要创建新线程或重用空闲线程。
4.6. 线程池的可扩展性
ExecutorService
可以与不同的线程池实现一起工作,如 FixedThreadPool
、CachedThreadPool
、ScheduledThreadPool
等,提供了高度的可扩展性和灵活性。
4.7. 异常处理
ExecutorService
提交的任务如果抛出异常,可以通过 Future
对象的 get
方法捕获这些异常。
4.8. 结果处理
Future
对象提供了 get
方法来获取任务结果,如果任务尚未完成,get
方法会阻塞直到任务完成。
4.9. 任务取消
Future
对象提供了 cancel
方法来取消任务,可以传入一个布尔值参数来决定是否中断正在执行的任务。
4.10. 线程工厂和拒绝策略
ExecutorService
可以使用自定义的线程工厂来创建线程,以及自定义的拒绝策略来处理任务提交过多时的情况。
ExecutorService
的设计提供了一个强大的框架,用于构建并发应用程序,它简化了并发编程的复杂性,同时提供了丰富的控制和灵活的配置选项。通过 ExecutorService
,开发者可以更容易地实现线程安全的异步任务执行。
5、线程池的种类
-
FixedThreadPool:
- 拥有固定数量线程的线程池,适用于负载较重的服务器。
- 拥有固定数量线程的线程池,适用于负载较重的服务器。
-
SingleThreadExecutor:
- 只有一个线程的线程池,用于顺序执行任务。
- 只有一个线程的线程池,用于顺序执行任务。
-
CachedThreadPool:
- 根据需要创建新线程的线程池,对于短生命周期的异步任务非常合适。
- 根据需要创建新线程的线程池,对于短生命周期的异步任务非常合适。
-
ScheduledThreadPool:
- 用于延迟执行或定期执行任务的线程池。
- 用于延迟执行或定期执行任务的线程池。
-
SingleThreadScheduledExecutor:
- 单个线程的变体,用于延迟或定时执行任务。
- 单个线程的变体,用于延迟或定时执行任务。
-
WorkStealingPool:
- 基于工作窃取算法的线程池,适用于并行计算。
- 基于工作窃取算法的线程池,适用于并行计算。
这些线程池都是通过Executors
工具类提供的工厂方法来创建的。除了这些,开发者还可以通过直接实例化ThreadPoolExecutor
类来创建自定义配置的线程池。
ExecutorService
接口本身并不定义线程池的具体实现,而是提供了一组通用的接口,用于管理和执行异步任务。不同的线程池实现提供了不同的功能和性能特性,以适应不同的并发场景。
6、ThreadPoolExecutor
线程池设计
简化版
- 核心参数初始化:包括核心线程数、最大线程数、任务队列、空闲线程存活时间和线程工厂等参数的初始化。
- 任务提交到线程池:当任务被提交到线程池时,线程池会根据当前的状态和参数来决定如何处理这个任务。
- 线程获取任务并执行:如果有空闲的核心线程,它会直接执行任务;如果没有空闲的核心线程但任务队列未满,任务会被添加到任务队列中。
- 创建非核心线程执行任务:如果任务队列已满且当前线程数小于最大线程数,会创建一个新的非核心线程来执行任务。
- 拒绝策略处理任务:如果任务队列已满且线程数达到最大线程数,任务将被拒绝,线程池会根据拒绝策略处理器来处理这个任务。
- 线程池状态管理:
ThreadPoolExecutor
维护一个ctl
变量,用于控制线程池的状态,包括 RUNNING、SHUTDOWN、STOP、TIDYING 和 TERMINATED 等状态。
详细版
- 创建 ThreadPoolExecutor:创建一个
ThreadPoolExecutor
实例,开始线程池的初始化过程。 - 核心参数初始化:初始化线程池的核心参数,包括核心线程数、最大线程数、任务队列、空闲线程存活时间和线程工厂。
- 任务提交到线程池:当任务被提交到线程池时,线程池会根据当前的状态和参数来决定如何处理这个任务。
- 线程获取任务并执行:如果有空闲的核心线程,它会直接执行任务;如果没有空闲的核心线程但任务队列未满,任务会被添加到任务队列中。
- 创建非核心线程执行任务:如果任务队列已满且当前线程数小于最大线程数,会创建一个新的非核心线程来执行任务。
- 拒绝策略处理任务:如果任务队列已满且线程数达到最大线程数,任务将被拒绝,线程池会根据拒绝策略处理器来处理这个任务。
- 线程尝试获取新任务:任务执行完毕后,线程会尝试从任务队列中获取新的任务。
- 线程销毁或等待新任务:如果任务队列空,线程会进入空闲状态,如果达到空闲线程存活时间,线程将被销毁。
- 线程池状态检查:线程池会根据其状态来决定是否停止接收新任务,是否中断运行中的任务,以及是否等待线程终止。
6.1 ThreadPoolExecutor应用
6.1.1. 服务器端处理请求
在服务器应用中,ThreadPoolExecutor
可以用来处理客户端的请求。服务器可以创建一个固定大小的线程池来同时处理多个请求,提高响应速度和吞吐量。
int corePoolSize = 10; // 核心线程数
int maximumPoolSize = 50; // 最大线程数
long keepAliveTime = 120; // 空闲线程存活时间
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); // 任务队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue
);
// 提交任务到线程池
executor.execute(new ClientRequestHandler());
6.1.2. 批量数据处理
在处理批量数据时,如文件处理或数据库批量操作,ThreadPoolExecutor
可以用来并行处理数据,提高处理速度。
List<Data> dataList = ...; // 待处理的数据列表
int threadCount = Runtime.getRuntime().availableProcessors(); // 使用可用处理器数量作为线程数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
threadCount,
threadCount,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()
);
dataList.forEach(data -> executor.execute(new DataProcessorTask(data)));
6.1.3. 异步任务执行
在需要异步执行任务的场景中,ThreadPoolExecutor
可以用来提交任务并在未来某个时刻获取结果。
Future<String> future = executor.submit(() -> {
// 异步执行的任务
return "任务结果";
});
// 获取异步任务的结果
String result = future.get();
6.1.4. 定时和周期性任务
ThreadPoolExecutor
可以与 ScheduledExecutorService
结合使用,来执行定时和周期性任务。
ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1);
scheduledExecutor.scheduleAtFixedRate(() -> {
// 定时执行的任务
}, 0, 10, TimeUnit.SECONDS);
6.1.5. 资源受限环境下的任务处理
在资源受限的环境中,如移动设备或嵌入式系统,ThreadPoolExecutor
可以用来合理分配有限的计算资源。
int maxThreads = 4; // 根据设备性能设置最大线程数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
maxThreads,
maxThreads,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<>()
);
7、ScheduledExecutorService
线程池设计
- ScheduledExecutorService:这是线程池的管理接口,负责提交和管理任务。
- 任务队列(Task Queue) :这是一个延迟队列,用于存储待执行的任务,按照预定的执行时间排序。
- 核心线程池(Core Thread Pool) :线程池中的核心线程会不断地从任务队列中取出任务并执行。
- 工作线程(Worker Thread) :线程池中的线程负责执行任务。
- 执行周期性任务(scheduleAtFixedRate) :用于安排任务以固定频率执行。
- 执行延迟任务(scheduleWithFixedDelay) :用于安排任务在每次执行完毕后按照固定延迟执行。
- 执行单次任务(schedule) :用于安排任务在指定延迟后执行一次。
- 任务完成:任务执行完毕后,如果是周期性任务,会重新调度下一次执行。
- 线程池关闭(Thread Pool Shutdown) :当不再需要线程池时,可以关闭线程池,等待所有任务完成或尝试立即停止所有任务。
7.1ScheduledExecutorService
应用案例
7.1.1. 一次性延迟执行任务
在这个例子中,我们创建了一个 ScheduledExecutorService
实例,并安排了一个任务在延迟一定时间后执行。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class DelayedTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("任务在延迟后执行:" + System.currentTimeMillis());
scheduler.schedule(task, 5, TimeUnit.SECONDS); // 5秒后执行
scheduler.shutdown(); // 执行完毕后关闭调度器
}
}
7.1.2. 固定速率周期执行任务
在这个例子中,我们安排了一个任务以固定的速率周期性执行。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class FixedRateTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("定期任务执行时间: " + System.currentTimeMillis());
scheduler.scheduleAtFixedRate(task, 0, 10, TimeUnit.SECONDS); // 每10秒执行一次
scheduler.shutdown(); // 执行完毕后关闭调度器
}
}
7.1.3. 固定延迟周期执行任务
在这个例子中,我们安排了一个任务在每次执行完毕后,等待固定的延迟时间再执行下一次。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class FixedDelayTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("带有固定延迟的任务执行时间: " + System.currentTimeMillis());
scheduler.scheduleWithFixedDelay(task, 0, 15, TimeUnit.SECONDS); // 每次执行完毕后等待15秒再执行
scheduler.shutdown(); // 执行完毕后关闭调度器
}
}
标签:ExecutorService,队列,设计图,任务,线程,设计,执行,ThreadPoolExecutor
From: https://blog.csdn.net/alises1314/article/details/143567267