Java中线程池是并发程序中常用的一种资源管理方式。使用线程池可以有效地管理和复用线程,避免了线程的频繁创建和销毁,从而提高了系统资源利用率,减少了系统开销。
Java中管理线程池的核心接口是Executor
,其下有一个重要的子接口ExecutorService
,以及一系列实现类,如ThreadPoolExecutor
和ScheduledThreadPoolExecutor
等。另外,Java的Executors
类提供了工厂方法来创建不同类型的线程池。
下面是几种常见线程池的使用示例:
1. 固定大小的线程池(FixedThreadPool)
当你希望线程池中的线程数量固定,并且每个提交的任务都要执行时,可以使用Executors.newFixedThreadPool
来创建一个固定大小的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(() -> {
// 这里是任务的代码
System.out.println(Thread.currentThread().getName() + " is running");
});
}
fixedThreadPool.shutdown();
2. 单线程的线程池(SingleThreadExecutor)
当你希望任务按照提交的顺序一次执行一个时,使用Executors.newSingleThreadExecutor
来创建一个单线程的线程池是合适的。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
singleThreadExecutor.execute(() -> {
// 这里是任务的代码
System.out.println(Thread.currentThread().getName() + " is running");
});
}
singleThreadExecutor.shutdown();
3. 可缓存的线程池(CachedThreadPool)
如果你希望线程池能够根据需要创建新线程,但当旧有线程可用时会复用它们,Executors.newCachedThreadPool
提供了一个能够根据需要创建新线程的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cachedThreadPool.execute(() -> {
// 这里是任务的代码
System.out.println(Thread.currentThread().getName() + " is running");
});
}
cachedThreadPool.shutdown();
4. 定时及周期性任务线程池(ScheduledThreadPool)
当你需要执行定时或周期性任务时,使用Executors.newScheduledThreadPool
创建一个调度线程池。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(() -> {
// 这里是需要周期执行的任务代码
System.out.println(Thread.currentThread().getName() + " is running");
}, 1, 3, TimeUnit.SECONDS); // 延迟1秒后每3秒执行一次
// 关闭线程池的代码通常放在程序的最后,或者在应用关闭时执行。
// scheduledThreadPool.shutdown();
注意事项
- 任务提交后处理流程:可使用
execute()
方法提交不需要返回值的任务,使用submit()
方法提交需要返回值的任务。 - 关闭线程池:在用完线程池后,应该调用
shutdown()
方法来关闭线程池,否则线程池内的线程不会自动退出,这可能会导致程序无法正常结束或者资源泄漏问题。 - 异常处理:在任务执行过程中可能会抛出异常,应当在任务代码中进行相应的异常处理。
- 自定义线程池:除了使用
Executors
类提供的工厂方法外,更推荐通过直接创建ThreadPoolExecutor
实例的方式来自定义线程池,可以更灵活地控制线程池的参数,例如核心线程数、最大线程数、线程空闲存活时间等。
以下是一个自定义线程池的简单示例:
自定义线程池(ThreadPoolExecutor)
如果Executors
提供的线程池不能满足需求,可以使用ThreadPoolExecutor
直接创建线程池,这样可以更精细地控制线程池的行为。
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
int corePoolSize = 5; // 核心线程数
int maximumPoolSize = 10; // 最大线程数
long keepAliveTime = 60; // 超出corePoolSize的线程的空闲时间
TimeUnit unit = TimeUnit.SECONDS; // keepAliveTime的单位
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); // 任务队列容量
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue
);
// 提交任务
for (int i = 0; i < 20; i++) {
threadPoolExecutor.execute(() -> {
// 这里是任务的代码
System.out.println(Thread.currentThread().getName() + " is running");
try {
TimeUnit.SECONDS.sleep(2); // 假设任务执行需要2秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
});
}
// 给线程池一些时间来执行任务
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
// 关闭线程池,等待已提交的任务全部执行完
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
// 超时后强制关闭
threadPoolExecutor.shutdownNow();
}
关键点解释:
- 核心线程数(
corePoolSize
):即使空闲了也会保留在池中的线程数。 - 最大线程数(
maximumPoolSize
):线程池中允许的最大线程数。 - 空闲线程存活时间(
keepAliveTime
):非核心线程闲置时在终止前等待新任务的最长时间。 - 时间单位(
unit
):keepAliveTime
参数的时间单位。 - 工作队列(
workQueue
):保存等待执行的任务的阻塞队列,这里使用了容量为100的LinkedBlockingQueue
。
异常处理建议
threadPoolExecutor.execute(() -> {
try {
// 任务代码
} catch (Exception e) {
// 异常处理逻辑
}
});
确保任务内部对所有可能的异常进行了处理,防止抛出未捕获的异常导致线程终止。
关闭线程池注意事项
shutdown()
方法启动线程池的关闭序列,不再接受新任务,但会执行所有已提交的任务。shutdownNow()
方法尝试停止所有正在执行的任务,并返回等待执行的任务列表。awaitTermination()
方法用于等待线程池的任务在关闭后终止。
正确地关闭线程池是编写健壮并发程序的关键部分,它能保证资源的正确释放和程序的有序退出。