在 Java 多线程编程中,线程的创建和销毁是有一定开销的,频繁地创建和销毁线程会消耗大量系统资源,降低程序性能。Java 线程池的出现,很好地解决了这个问题,它通过对线程的复用,提高了系统资源的利用率,同时还能对线程进行有效的管理。
一、线程池基础概念
线程池是一种管理和控制线程资源的机制,它维护着一组线程,这些线程可以被重复使用来执行多个任务。线程池主要包含以下几个关键组成部分:
- 核心线程数:线程池中始终保持存活的线程数量,即使这些线程处于空闲状态,也不会被销毁。
- 最大线程数:线程池允许创建的最大线程数量。当任务队列已满且有新任务提交时,若当前线程数小于最大线程数,线程池会创建新的线程来处理任务。
- 任务队列:用于存放等待执行的任务。当线程池中的线程都处于忙碌状态时,新提交的任务会被放入任务队列中等待执行。
- 线程工厂:用于创建线程的工厂,通过它可以自定义线程的创建方式,例如设置线程的名称、优先级等属性。
- 拒绝策略:当线程池中的线程数量达到最大线程数且任务队列也已满时,新提交的任务将无法被处理,此时就需要采取拒绝策略来处理这些任务。
二、线程池的创建
在 Java 中,我们可以通过ThreadPoolExecutor类来创建线程池,其构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:当线程数大于核心线程数时,多余的空闲线程存活的最长时间。
- unit:keepAliveTime的时间单位,如TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)等。
- workQueue:任务队列,用于保存等待执行的任务,常见的有ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)等。
- threadFactory:线程工厂,用于创建线程。
- handler:拒绝策略,当任务无法被执行时的处理策略,常见的拒绝策略有AbortPolicy(抛出异常)、CallerRunsPolicy(在调用者线程中执行任务)、DiscardPolicy(丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务,然后重新尝试执行任务) 。
以下是一个简单的线程池创建示例:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数为5
10, // 最大线程数为10
60, // 空闲线程存活时间为60秒
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界任务队列,容量为100
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略为在调用者线程中执行任务
);
// 提交任务
for (int i = 0; i < 200; i++) {
executor.submit(() -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 执行任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
除了直接使用ThreadPoolExecutor创建线程池,Java 还提供了一些工具类来创建常见的线程池,例如Executors类:
- newFixedThreadPool:创建固定大小的线程池,核心线程数和最大线程数相等,任务队列是无界队列。
- newCachedThreadPool:创建可缓存的线程池,核心线程数为 0,最大线程数为Integer.MAX_VALUE,任务队列是SynchronousQueue(不存储元素的队列,每个插入操作必须等待另一个线程的移除操作)。
- newSingleThreadExecutor:创建只有一个线程的线程池,保证所有任务按照顺序执行。
- newScheduledThreadPool:创建支持定时及周期性任务执行的线程池。
三、线程池的使用
线程池创建好之后,我们可以通过submit或execute方法来提交任务。submit方法用于提交返回值的任务(实现Callable接口的任务),它会返回一个Future对象,通过该对象可以获取任务的执行结果;execute方法用于提交无返回值的任务(实现Runnable接口的任务)
import java.util.concurrent.*;
public class ThreadPoolUsage {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
new ThreadPoolExecutor.AbortPolicy()
);
// 提交无返回值任务
executor.execute(() -> {
System.out.println("无返回值任务执行");
});
// 提交有返回值任务
Future<Integer> future = executor.submit(() -> {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
});
try {
System.out.println("有返回值任务结果:" + future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
四、线程池使用注意事项
- 合理设置线程池参数:核心线程数、最大线程数和任务队列的大小需要根据任务的特点和系统资源情况进行合理设置。如果核心线程数设置过小,可能导致任务处理速度慢;如果设置过大,可能会占用过多系统资源。任务队列的大小也需要根据实际情况调整,无界队列可能会导致内存溢出。
- 正确选择拒绝策略:不同的拒绝策略适用于不同的场景。例如,在对任务执行可靠性要求较高的场景下,不适合使用DiscardPolicy和DiscardOldestPolicy;而在一些允许任务丢失的场景中,可以考虑使用这两种策略。
- 及时关闭线程池:当线程池不再使用时,应及时调用shutdown或shutdownNow方法关闭线程池。shutdown方法会等待已提交的任务执行完毕后再关闭线程池,而shutdownNow方法会尝试停止所有正在执行的任务,并返回等待执行的任务列表。
- 避免任务阻塞线程池:如果任务执行过程中出现长时间阻塞(例如等待外部资源、死锁等),会导致线程池中的线程无法及时释放,影响其他任务的执行。因此,在编写任务代码时,要尽量避免出现阻塞情况,或者对可能阻塞的操作设置合理的超时时间。
- 监控线程池状态:在生产环境中,建议对线程池的运行状态进行监控,例如线程池中的活跃线程数、任务队列的大小、任务的执行时间等。通过监控这些指标,可以及时发现线程池可能存在的问题,并进行相应的调整和优化。

7591

被折叠的 条评论
为什么被折叠?



