文章目录
- 线程池概述
- 线程池的核心组件和概念:
- 线程池的工作原理:
- 线程池的创建:
- 线程池的关闭:
- 总结:
- 线程池种类以及用途
- 以ScheduledThreadPool为例的类继承关系
- 拒绝策略有哪些?
- 使用范例
- 固定大小的线程池
- 单线程执行器
- 可缓存的线程池
- 定时及周期性任务的线程池
线程池概述
Java线程池(
java.util.concurrent.ThreadPoolExecutor
)是一种执行器(Executor),用于在一个后台线程中执行任务。线程池的主要目的是减少在创建和销毁线程时所产生的性能开销。通过重用已经创建的线程来执行新的任务,线程池提高了程序的响应速度,并且提供了更好的系统资源管理。
线程池的核心组件和概念:
-
核心线程数(Core Pool Size):
线程池中始终保持的线程数量,即使它们处于空闲状态。 -
最大线程数(Maximum Pool Size):
线程池中允许的最大线程数量。 -
工作队列(Work Queue):
用于存放待执行任务的阻塞队列。 -
线程工厂(Thread Factory):
用于创建新线程的工厂。可以用来设置线程的名称,以便在日志或监控中区分不同线程。 -
拒绝策略(Rejected Execution Handler):
当任务太多,无法被线程池及时处理时,采取的策略。常见的拒绝策略包括丢弃任务、抛出异常、使用调用者线程执行任务等。 -
保持活动时间(Keep-Alive Time):
非核心线程空闲时在终止前等待新任务的最长时间。 -
时间单位(Time Unit):
保持活动时间的时间单位,如秒、毫秒等。
线程池的工作原理:
-
提交任务:
当一个任务被提交给线程池时,线程池会尝试使用空闲的核心线程来执行任务。 -
核心线程忙碌:
如果所有核心线程都忙碌,且工作队列已满,线程池会创建一个非核心线程来处理任务,直到达到最大线程数。 -
任务队列:
如果线程池中的线程都忙碌,且工作队列未满,新任务会被放入工作队列等待。 -
拒绝策略:
如果线程池和工作队列都达到了最大容量,线程池会使用拒绝策略来处理新任务。 -
线程复用:
线程池会重用执行完任务的线程,而不是销毁它们,以提高性能。 -
线程终止:
当非核心线程空闲时间超过指定的保持活动时间,且工作队列为空时,线程池会终止这些线程。
线程池的创建:
Java提供了Executors
类来创建预定义配置的线程池:
-
固定大小的线程池:
int nThreads = 10; ExecutorService fixedThreadPool = Executors.newFixedThreadPool(nThreads);
-
单线程池:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
-
可缓存的线程池:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
-
定时及周期性任务的线程池:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
线程池的关闭:
当不再需要线程池时,应该正确地关闭它,以释放资源:
executorService.shutdown(); // 停止接收新任务,尝试完成已提交的任务
executorService.shutdownNow(); // 尝试立即终止所有正在执行的任务,并返回等待执行的任务列表
总结:
线程池是现代并发编程中不可或缺的一部分,它提供了一种有效的方式来管理并发任务。通过合理配置线程池,可以提高程序的性能和资源利用率。然而,不当的配置也可能导致资源浪费或性能问题,因此需要根据具体的应用场景来调整线程池的参数。
线程池种类以及用途
Java中的线程池类型主要可以通过
java.util.concurrent
包中的Executors
类来创建,以下是几种常见的线程池类型及其用途:
-
FixedThreadPool(固定大小的线程池):
- 特点:拥有固定数量的线程,这些线程会在池中一直存在,直到线程池被关闭。
- 用途:适用于负载较重的服务器,处理请求比较平均,可以控制最大并发数,适合于短生命周期的长任务。
-
SingleThreadExecutor(单线程执行器):
- 特点:只有一个线程在工作,所有任务按顺序执行。
- 用途:适用于需要保证任务顺序执行的场景,或者需要单个后台线程处理任务的场景。
-
CachedThreadPool(可缓存的线程池):
- 特点:对于短生命周期的长任务非常合适,它会根据需要创建新线程,对于短生命周期的长任务非常合适,如果线程空闲超过60秒,则会被回收。
- 用途:适用于短生命周期的短任务,可以动态回收空闲线程,减少资源占用。
-
ScheduledThreadPool(预定线程池):
- 特点:用于延迟执行或定期执行任务。
- 用途:适用于需要定时执行任务的场景,如调度任务、周期性执行任务等。
-
WorkStealingPool(工作窃取线程池):
- 特点:利用
ForkJoinPool
实现,能够将一个大任务分解为多个小任务,并且可以跨线程执行这些小任务。 - 用途:适用于可以并行计算的任务,特别是可以分解为多个小任务的场景,如并行计算、大数据处理等。
- 特点:利用
-
ForkJoinPool(叉Join线程池):
- 特点:专为并行计算设计,利用工作窃取算法,能够高效地处理可以分解为多个小任务的场景。
- 用途:适用于可以并行处理的计算密集型任务,如科学计算、大数据处理等。
-
自定义线程池:
- 特点:通过直接实例化
ThreadPoolExecutor
类来创建,可以自定义线程池的各项参数,如核心线程数、最大线程数、工作队列、线程工厂、拒绝策略等。 - 用途:适用于需要特定线程池参数的场景,可以根据实际需求灵活配置线程池。
- 特点:通过直接实例化
- 上述的线程池中
ScheduledThreadPool
,ForkJoinPool
这两个线程池以独立类的形式存在, 剩下的线程池都是以ThreadPoolExecutor
为基础, 通过不同的参数衍生出来的线程池。
以ScheduledThreadPool为例的类继承关系
每种线程池都有其特定的使用场景,选择合适的线程池类型可以提高程序的性能和资源利用率。在实际应用中,应根据任务的特性和需求来选择最合适的线程池类型。
拒绝策略有哪些?
在Java的线程池中,当任务太多,无法被线程池及时处理时,就需要使用拒绝策略(Rejected Execution Handler)。以下是Java线程池中常用的拒绝策略:
-
AbortPolicy:
- 默认拒绝策略。当任务被拒绝时,会抛出
RejectedExecutionException
异常。
- 默认拒绝策略。当任务被拒绝时,会抛出
-
CallerRunsPolicy:
- 该策略将任务回退到调用者,即由调用者线程来运行任务。如果调用者线程忙,则任务会等待,但这至少可以保证任务不会丢失。
-
DiscardPolicy:
- 该策略默默地丢弃无法处理的任务,不抛出异常也不记录日志。
-
DiscardOldestPolicy:
- 该策略丢弃工作队列中最早的未处理任务,并尝试再次提交当前任务。
除了上述四种预定义的拒绝策略外,你还可以自定义拒绝策略,通过实现RejectedExecutionHandler
接口来定义自己的处理逻辑。
示例代码,自定义拒绝策略:
public class MyRejectedExecutionHandler implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 自定义处理逻辑// 例如,记录日志、将任务添加到其他队列等System.out.println("Task " + r.toString() + " rejected from " + executor.toString());}
}
然后在创建线程池时,可以设置自定义的拒绝策略:
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,timeUnit,workQueue,new MyRejectedExecutionHandler()
);
选择合适的拒绝策略对于确保线程池在高负载情况下的正确行为至关重要。开发者应根据应用的具体需求和异常处理策略来选择或实现拒绝策略。
使用范例
下面是一个使用Java线程池的示例,其中包含了线程池的创建、提交任务、关闭线程池等操作。
固定大小的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class FixedThreadPoolExample {public static void main(String[] args) {// 创建一个固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(4);// 提交任务给线程池执行for (int i = 0; i < 8; i++) {int taskNumber = i;executor.submit(() -> {System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 关闭线程池// shutdown() 方法会平滑地关闭线程池,不再接受新任务,但会处理已提交的任务executor.shutdown();// 等待所有任务完成,或者超时try {// 等待线程池中所有任务完成executor.awaitTermination(30, TimeUnit.SECONDS);} catch (InterruptedException e) {System.out.println("Tasks interrupted");}System.out.println("Finished all tasks");}
}
单线程执行器
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SingleThreadExecutorExample {public static void main(String[] args) {// 创建一个单线程执行器ExecutorService executor = Executors.newSingleThreadExecutor();// 提交任务给单线程执行器执行for (int i = 0; i < 5; i++) {int taskNumber = i;executor.submit(() -> {System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());});}// 关闭单线程执行器executor.shutdown();}
}
可缓存的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class CachedThreadPoolExample {public static void main(String[] args) {// 创建一个可缓存的线程池ExecutorService executor = Executors.newCachedThreadPool();// 提交任务给线程池执行for (int i = 0; i < 10; i++) {int taskNumber = i;executor.submit(() -> {System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 关闭线程池executor.shutdown();}
}
定时及周期性任务的线程池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolExample {public static void main(String[] args) {// 创建一个定时及周期性任务的线程池ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);// 定时任务executor.schedule(() -> {System.out.println("Scheduled task executed on thread " + Thread.currentThread().getName());}, 10, TimeUnit.SECONDS);// 周期性任务executor.scheduleAtFixedRate(() -> {System.out.println("Periodic task executed on thread " + Thread.currentThread().getName());}, 0, 5, TimeUnit.SECONDS);// 关闭线程池(注意:关闭前需要等待一段时间,以确保任务执行完成)Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("Shutting down executor");executor.shutdown();try {if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {executor.shutdownNow();}} catch (InterruptedException e) {executor.shutdownNow();}}));}
}
这些示例展示了如何使用Java中的不同类型线程池来执行任务。在实际应用中,你可以根据任务的特性和需求选择合适的线程池类型。记得在不再需要线程池时正确关闭它,以释放系统资源。