面试题答案
一键面试1. 线程池参数调优
- 核心线程数:根据任务类型和系统资源确定。若任务为CPU密集型,核心线程数可设置为CPU核心数 + 1,让CPU在偶尔的线程切换开销下也能保持忙碌。如在多核服务器上,对于计算密集型任务,可通过
Runtime.getRuntime().availableProcessors() + 1
获取合适的核心线程数。若为I/O密集型任务,由于线程大部分时间在等待I/O操作完成,核心线程数可适当增大,例如设置为CPU核心数的2倍。 - 最大线程数:合理设置最大线程数避免资源耗尽。对于I/O密集型任务,最大线程数可适当比核心线程数大,以利用更多空闲线程处理I/O等待。但对于CPU密集型任务,最大线程数不宜比核心线程数大太多,否则过多的线程切换会消耗大量CPU资源。
- 队列容量:有界队列避免内存耗尽。若任务执行时间短且提交频率高,队列容量可设置稍大,以缓冲任务。若任务执行时间长,队列容量不宜过大,防止大量任务堆积导致内存占用过高。无界队列(如
LinkedBlockingQueue
不指定容量)可能会导致OOM,应谨慎使用。
2. 任务特性考虑
- 任务执行时间:将长任务和短任务分开处理。可使用两个线程池,一个处理长任务,设置较少的核心线程数和队列容量;另一个处理短任务,设置较多的核心线程数和较大的队列容量。例如,对于数据库查询等长任务和简单的日志记录等短任务,分别用不同线程池处理。
- 任务优先级:使用
PriorityBlockingQueue
结合实现Comparable
接口的任务类,给任务设置优先级。在任务提交时,根据业务需求分配优先级,让线程池优先处理高优先级任务。
3. 系统资源考量
- CPU:监控CPU使用率,避免线程过多导致CPU上下文切换开销过大。可通过工具如
top
(Linux)或Task Manager
(Windows)查看CPU负载。若CPU使用率过高,减少线程数;若CPU利用率低,适当增加线程数。 - 内存:控制任务队列和线程数,防止内存溢出。监控内存使用情况,如通过
jconsole
等工具查看Java堆内存使用。当内存接近上限时,调整队列容量或线程数。
4. 代码示例
优化前
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class UnoptimizedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 100; i++) {
int taskNumber = i;
executorService.submit(() -> {
try {
// 模拟任务执行时间
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("Task " + taskNumber + " completed.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
优化后
import java.util.concurrent.*;
public class OptimizedThreadPool {
public static void main(String[] args) {
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
int maximumPoolSize = corePoolSize * 2;
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
ThreadPoolExecutor executorService = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue);
for (int i = 0; i < 100; i++) {
int taskNumber = i;
executorService.submit(() -> {
try {
// 模拟任务执行时间
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("Task " + taskNumber + " completed.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
在上述代码示例中,优化前使用了Executors.newFixedThreadPool(5)
创建固定5个线程的线程池。优化后根据CPU核心数动态设置核心线程数和最大线程数,并使用有界队列。在高并发场景下,优化后的线程池能更合理地利用系统资源,减少任务等待时间,提高整体并发处理能力。例如,通过性能测试工具(如JMeter)模拟大量任务提交,优化后的线程池平均响应时间会更短,吞吐量会更高。