目录
3.3 ScheduledThreadPool(定时任务线程池)
一、Java线程池介绍
在Java中,线程池是一种管理和复用线程的机制,用于提高多线程应用程序的性能和资源利用率。线程池在执行任务时,可以避免频繁地创建和销毁线程,从而减少了系统开销,并且能够更有效地利用系统资源。Java中线程池的主要作用包括以下方面:
① 线程的复用:线程池会预先创建一定数量的线程,并将它们保存在池中。当有任务需要执行时,线程池会分配一个空闲的线程来执行任务,执行完毕后线程不会销毁,而是重新放入线程池中,等待下一个任务的到来。这种线程的复用避免了频繁地创建和销毁线程,提高了线程的利用率。
② 资源管理:线程池可以限制并发线程的数量,避免系统因为线程过多而导致资源耗尽或性能下降的问题。通过配置线程池的核心参数,可以控制线程数量的上限、空闲线程的存活时间等,从而更好地管理系统资源。
③ 任务调度:线程池可以用于执行各种类型的任务,包括周期性任务、延迟任务、定时任务等。通过使用不同类型的线程池和调度策略,可以灵活地调度任务的执行时间和执行方式,满足不同场景下的需求。
Java中线程池的实现主要依赖于java.util.concurrent包下的Executor接口及其子接口ExecutorService,以及ThreadPoolExecutor等具体实现类。开发人员可以通过这些接口和类来创建和管理线程池,并通过配置不同的参数来满足不同的需求。
二、几种常见的线程池
2.1 FixedThreadPool
FixedThreadPool(固定大小线程池):该线程池会一直保持在核心线程数的数量不变,即使有空闲线程。当有新任务提交时,如果所有核心线程都在执行任务,新任务会被放入队列中等待。这种线程池适用于负载较重的服务器。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小为3的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " completed.");
});
}
executor.shutdown();
}
}
2.2 CachedThreadPool
CachedThreadPool(缓存线程池):该线程池会根据需要自动创建新线程,但在一定的时间范围内会重用之前创建的线程。如果线程长时间空闲,它会被回收,从而减少系统资源的占用。适用于执行大量短期异步任务的情况。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
// 创建一个可缓存线程池
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " completed.");
});
}
executor.shutdown();
}
}
2.3 ScheduledThreadPool
ScheduledThreadPool(定时任务线程池):该线程池可用于执行定时任务和周期性任务。它可以指定核心线程数,当任务执行时间超过线程池核心线程数时,会创建新线程来处理。
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(3);
executor.scheduleAtFixedRate(() -> {
System.out.println("Task executing by " + Thread.currentThread().getName());
}, 0, 3, TimeUnit.SECONDS); // 初始延迟0秒,每3秒执行一次任务
}
}
2.4 SingleThreadPool
SingleThreadPool(单线程线程池):该线程池只有一个核心线程,所有任务按照指定顺序执行,即保证了任务的顺序性。如果该线程异常结束,会重新创建一个新的线程来替代。适用于需要顺序执行任务且保证线程安全的情况。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadPoolExample {
public static void main(String[] args) {
// 创建一个单线程线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " completed.");
});
}
executor.shutdown();
}
}
2.5 WorkStealingPool
WorkStealingPool(工作窃取线程池):Java 8引入了这种新型线程池,它的核心思想是让空闲的线程从其他任务队列中窃取任务来执行,以提高线程利用率。这种线程池适用于处理大量耗时较长的任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class WorkStealingPoolExample {
public static void main(String[] args) throws InterruptedException {
// 获取当前系统的处理器数量作为线程池大小
int processors = Runtime.getRuntime().availableProcessors();
// 创建工作窃取线程池
ExecutorService executor = Executors.newWorkStealingPool(processors);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " completed.");
});
}
executor.awaitTermination(10, TimeUnit.SECONDS);
executor.shutdown();
}
}
三、线程池的运用场景
3.1 FixedThreadPool(固定大小线程池)
- 运用场景:适用于需要控制并发线程数量的场景,如服务器端的并发处理任务。比如,处理客户端请求的 Web 服务器,通常会预先分配一定数量的线程用于处理请求,以避免过多的线程导致资源竞争或资源耗尽。
- 优势:固定大小线程池可以保证同时执行的线程数量是有限的,避免了因为线程数量过多而导致的资源竞争和系统资源的浪费。
3.2 CachedThreadPool(缓存线程池)
- 运用场景:适用于需要处理大量短期异步任务的场景,任务执行时间短且任务数量不确定的情况。比如,处理用户请求的 Web 服务器,用户的请求可能会随时增加或减少,使用缓存线程池可以根据需求动态调整线程数量,避免了创建过多的线程而浪费系统资源。
- 优势:能够根据需求动态调整线程池的大小,灵活性较高,适应性强。
3.3 ScheduledThreadPool(定时任务线程池)
- 运用场景:适用于需要执行定时任务或周期性任务的场景。比如,定时数据备份、定时任务调度等。此外,也适用于需要同时处理多个延迟任务的情况。
- 优势:能够按照预定的时间间隔或延迟执行任务,方便管理和调度。
3.4 SingleThreadPool(单线程线程池)
- 运用场景:适用于需要保证任务的顺序执行和线程安全的场景。比如,文件操作、数据库操作等需要顺序执行的任务。
- 优势:保证任务按顺序执行,避免了多线程情况下可能出现的竞争和同步问题。
3.5 WorkStealingPool(工作窃取线程池)
- 运用场景:适用于需要处理大量耗时较长的任务的情况,任务之间可能存在依赖关系,需要充分利用系统资源来提高并行度和性能。
- 优势:能够动态地在多个任务队列之间进行工作窃取,提高了线程的利用率,同时能够更好地利用多核处理器的并行计算能力。
四、线程池创建时的核心参数
4.1 corePoolSize(核心线程数)
- 含义:线程池中保持活跃的核心线程数量,即使这些线程当前没有执行任务。
- 默认值:通常情况下,线程池在没有接收到任务时不会创建任何线程。但是,如果你希望线程池在创建时就立即创建一定数量的线程,则可以设置该参数,使得线程池在空闲时也能保持一定数量的核心线程。
4.2 maximumPoolSize(最大线程数)
- 含义:线程池允许创建的最大线程数。
- 默认值:通常情况下,线程池在达到核心线程数后,如果继续有新的任务提交,它会创建新的线程来执行任务,直到线程数量达到最大线程数。超过最大线程数的任务会被拒绝执行。
4.3 keepAliveTime(线程空闲时间)
- 含义:当线程池中的线程数量超过核心线程数时,多余的空闲线程在终止之前等待新任务的最长时间。
- 默认值:默认情况下,只有当线程池中的线程数量超过核心线程数时,才会考虑终止空闲线程。该参数指定了空闲线程的最长等待时间。超过这个时间后,多余的空闲线程将被终止,直到线程数量不超过核心线程数。
4.4 unit(线程空闲时间单位)
- 含义:线程空闲时间的单位,通常与keepAliveTime一起使用。
- 默认值:常见的单位包括TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)等。
4.5 workQueue(工作队列)
- 含义:用于保存等待执行的任务的阻塞队列。
- 默认值:Java提供了多种实现,如LinkedBlockingQueue、ArrayBlockingQueue等。具体选择哪种队列取决于你的需求和场景。一般情况下,如果任务频繁产生而执行较慢时,推荐使用无界队列;如果任务产生的速度大于处理的速度,可以考虑使用有界队列,以避免无限制的任务增长。
4.6 threadFactory(线程工厂)
- 含义:用于创建新线程的工厂类。
- 默认值:如果不显式指定线程工厂,线程池会使用默认的线程工厂来创建线程。你可以通过实现ThreadFactory接口来自定义线程创建的过程,比如设置线程的名称、优先级等信息。
4.7 handler(拒绝策略)
- 含义:当工作队列已满且无法继续接收新任务时,线程池会采取的策略。
- 默认值:Java提供了多种拒绝策略,如AbortPolicy(默认策略,直接抛出RejectedExecutionException异常)、CallerRunsPolicy(由提交任务的线程执行被拒绝的任务)、DiscardPolicy(直接丢弃被拒绝的任务)、DiscardOldestPolicy(丢弃工作队列中最老的任务,然后尝试重新提交被拒绝的任务)等。你也可以实现RejectedExecutionHandler接口来定义自己的拒绝策略。