面试题答案
一键面试自定义线程池
在使用CompletableFuture
时,可以通过Executor
接口的实现类来自定义线程池。通常使用ThreadPoolExecutor
来创建线程池。以下是一个简单示例:
import java.util.concurrent.*;
import java.util.concurrent.CompletableFuture;
public class CompletableFutureCustomThreadPool {
public static void main(String[] args) {
// 创建自定义线程池
ExecutorService executor = new ThreadPoolExecutor(
2,
4,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10));
CompletableFuture.supplyAsync(() -> {
// 异步任务逻辑
System.out.println("异步任务执行中...");
return "任务结果";
}, executor)
.thenAccept(result -> System.out.println("结果: " + result))
.whenComplete((r, e) -> executor.shutdown());
}
}
在上述代码中,ThreadPoolExecutor
构造函数的参数含义如下:
corePoolSize
:线程池中核心线程的数量。核心线程会一直存活,即使处于闲置状态也不会被销毁,除非设置了allowCoreThreadTimeOut
为true
。maximumPoolSize
:线程池中允许的最大线程数。当任务队列已满且活动线程数小于最大线程数时,线程池会创建新的线程来处理任务。keepAliveTime
:非核心线程闲置时的存活时间。超过这个时间,非核心线程会被销毁。unit
:keepAliveTime
的时间单位。workQueue
:任务队列,用于存放等待执行的任务。
不同大小和类型线程池对异步任务执行性能的影响
- 核心线程数:
- 过小:如果核心线程数过小,在任务较多时,可能导致任务长时间等待核心线程可用,从而增加任务的响应时间。例如,在一个处理大量HTTP请求的Web应用中,如果核心线程数设置为1,而同时有多个请求进来,后续请求就需要等待前一个请求处理完,导致整体响应变慢。
- 过大:核心线程数过大可能会占用过多系统资源,因为即使线程处于闲置状态也不会被销毁(默认情况)。这可能导致系统资源浪费,并且可能影响其他应用程序的性能。
- 最大线程数:
- 过小:当任务队列已满且活动线程数达到最大线程数时,新的任务将无法立即执行,只能在队列中等待,可能导致任务堆积,影响性能。比如在高并发的文件下载场景中,如果最大线程数设置过小,大量下载请求无法及时处理,会导致用户等待时间过长。
- 过大:如果最大线程数设置过大,可能会导致过多的线程竞争系统资源,如CPU、内存等,从而导致上下文切换开销增大,降低系统整体性能。
- 任务队列:
- 有界队列:有界队列可以防止任务无限堆积,避免内存耗尽。但如果队列容量过小,可能导致任务很快填满队列,进而触发创建新线程(直到达到最大线程数),增加线程创建开销。例如,在一个订单处理系统中,有界队列容量过小,订单处理任务可能很快填满队列,导致频繁创建新线程。
- 无界队列:无界队列可以一直添加任务,但可能会导致内存占用不断增加,甚至引发OOM(OutOfMemory)异常。例如,在一个日志收集系统中,如果使用无界队列,大量日志数据不断涌入,可能会耗尽内存。
结合实际场景选择合适的线程池配置
- CPU密集型任务:
- 特点:这类任务主要消耗CPU资源,如复杂的计算任务。
- 配置建议:核心线程数和最大线程数应设置为接近CPU核心数,通常可以设置为
Runtime.getRuntime().availableProcessors()
。这样可以充分利用CPU资源,避免过多线程导致的上下文切换开销。例如,在进行大数据分析中的复杂计算任务时,线程数设置为CPU核心数可以提高计算效率。
- I/O密集型任务:
- 特点:这类任务主要等待I/O操作完成,如文件读写、网络请求等。在等待I/O的过程中,线程处于空闲状态,不占用CPU资源。
- 配置建议:核心线程数可以设置得比CPU核心数多一些,因为在I/O等待期间,线程可以让出CPU资源给其他线程。例如,可以设置为
2 * Runtime.getRuntime().availableProcessors()
。最大线程数根据系统资源和预估的并发量适当调整,任务队列可以选择有界队列,并根据预估的任务量设置合适的容量。比如在一个网络爬虫应用中,由于需要大量的网络I/O操作,适当增加核心线程数可以提高爬虫效率。
- 混合型任务:
- 特点:任务既包含CPU密集型操作,又包含I/O密集型操作。
- 配置建议:需要综合考虑CPU和I/O的比例。如果I/O操作占比较大,可以参考I/O密集型任务的配置;如果CPU操作占比较大,则参考CPU密集型任务的配置。也可以通过性能测试来确定最优的线程池配置。例如,在一个图像处理应用中,既需要进行图像的计算处理(CPU密集),又需要读写图像文件(I/O密集),可以通过多次测试不同线程池配置下的性能来确定最佳参数。