线程池基础概念
线程池(Thread Pool)是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池技术能够很好地控制线程的数量和执行过程,是Java并发编程中的核心组件。
为什么需要线程池
1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
2. 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行
3. 提高线程可管理性:线程是稀缺资源,无限制创建会消耗系统资源,使用线程池可以进行统一分配、调优和监控
Java中的线程池实现
Java通过`java.util.concurrent.Executor`框架提供线程池功能,主要实现类是`ThreadPoolExecutor`,其构造函数参数包括:
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
线程池核心参数详解
1. 核心线程数(corePoolSize)
线程池中保持存活的最小线程数量,即使它们处于空闲状态。除非设置了`allowCoreThreadTimeOut`参数。
2. 最大线程数(maximumPoolSize)
线程池允许创建的最大线程数量。当工作队列满时,线程池会创建新线程直到达到此数量。
3. 存活时间(keepAliveTime)
当线程数大于核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。
4. 工作队列(workQueue)
用于保存等待执行的任务的阻塞队列,常见实现有:
- `ArrayBlockingQueue`:基于数组的有界队列
- `li
- `SynchronousQueue`:不存储元素的队列,每个插入操作必须等待另一个线程的移除操作
- `PriorityBlockingQueue`:具有优先级的无界队列
5. 线程工厂(threadFactory)
用于创建新线程的工厂,可以自定义线程名称、优先级等属性。
6. 拒绝策略(rejectedExecutionHandler)
当线程池和工作队列都饱和时,对新任务的处理策略:
- `AbortPolicy`(默认):抛出RejectedExecutionException
- `CallerRunsPolicy`:由调用线程执行该任务
- `DiscardPolicy`:直接丢弃任务
- `DiscardOldestPolicy`:丢弃队列中最老的任务,然后尝试重新提交当前任务
常见面试问题解析
1. 线程池的工作流程
1. 提交任务后,线程池首先检查当前线程数是否小于核心线程数
2. 如果小于,则创建新线程执行任务
3. 如果达到核心线程数,则将任务加入工作队列
4. 如果队列已满且线程数小于最大线程数,则创建新线程执行任务
5. 如果线程数已达到最大值且队列已满,则执行拒绝策略
2. 如何合理配置线程池参数
- CPU密集型任务:建议线程数设置为CPU核心数+1
- IO密集型任务:建议线程数设置为2CPU核心数
- 混合型任务:可以拆分为CPU密集和IO密集部分分别处理
实际应用中需要考虑:
- 任务特性(CPU/IO密集型)
- 系统资源限制
- 任务优先级
- 任务执行时间
3. 线程池的四种常见类型
Java通过`Executors`提供了四种常见线程池:
1. FixedThreadPool:固定大小线程池
java
Executors.newFixedThreadPool(int nThreads)
2. CachedThreadPool:可缓存线程池
java
Executors.newCachedThreadPool()
3. SingleThreadExecutor:单线程线程池
java
Executors.newSingleThreadExecutor()
4. ScheduledThreadPool:定时任务线程池
java
Executors.newScheduledThreadPool(int corePoolSize)
4. 线程池的关闭方法
- `shutdown()`:平缓关闭,不再接受新任务,但会处理完已提交的任务
- `shutdownNow()`:立即关闭,尝试停止所有正在执行的任务,返回等待执行的任务列表
5. 线程池的监控
可以通过以下方法监控线程池状态:
- `getTaskCount()`:获取已执行和未执行的任务总数
- `getCompletedTaskCount()`:获取已完成的任务数
- `getPoolSize()`:获取当前线程数
- `getActiveCount()`:获取活动线程数
实战技巧与最佳实践
1. 自定义线程工厂
java
public class CustomThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
CustomThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
2. 自定义拒绝策略
java
public class CustomRejectionPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录日志
System.err.println("Task " + r.toString() + " rejected from " + executor.toString());
// 可以选择重新尝试或持久化任务等处理方式
}
}
3. 使用ThreadLocal的注意事项
在线程池中使用ThreadLocal时,必须确保在任务完成后清理ThreadLocal变量,否则可能导致内存泄漏或数据污染。
4. 异常处理
线程池中的任务如果抛出未捕获异常,默认会打印堆栈跟踪并终止线程。可以通过以下方式处理:
java
ExecutorService executor = Executors.newFixedThreadPool(1);
Future future = executor.submit(() -> {
try {
// 任务代码
} catch (Exception e) {
// 处理异常
}
});
try {
future.get(); // 获取执行结果或异常
} catch (ExecutionException e) {
// 处理任务中抛出的异常
}
高级话题
1. ForkJoinPool
Java 7引入的适用于分治算法的线程池,采用工作窃取(work-stealing)算法提高并行效率。
2. CompletableFuture
Java 8引入的异步编程工具,内部使用ForkJoinPool.commonPool()或自定义线程池执行任务。
3. Spring中的线程池
Spring提供了`ThreadPoolTaskExecutor`作为线程池实现,支持更灵活的配置和与Spring生态的集成。
总结
线程池是Java并发编程中的重要组件,合理使用线程池可以显著提高系统性能。面试中关于线程池的问题通常围绕其工作原理、参数配置、使用场景和问题排查展开。掌握线程池的核心概念和最佳实践,不仅有助于面试表现,也能在实际开发中编写出更高效、更稳定的并发代码。