面试题答案
一键面试线程池方案设计
- 选择合适的线程池类型:
- 考虑使用
ThreadPoolExecutor
,因为它提供了更灵活的参数配置。对于这种混合任务场景,CachedThreadPool
不太合适,因为它会不断创建新线程,可能导致内存资源耗尽。FixedThreadPool
固定线程数,对于长生命周期任务可能会导致其他短生命周期任务长时间等待。所以ThreadPoolExecutor
是较好选择。 - 示例代码:
int corePoolSize = Runtime.getRuntime().availableProcessors(); int maximumPoolSize = corePoolSize * 2; long keepAliveTime = 10L; TimeUnit unit = TimeUnit.SECONDS; BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100); ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue );
- 考虑使用
- 合理配置参数:
- corePoolSize:设置为
Runtime.getRuntime().availableProcessors()
,这样能充分利用CPU核心数处理任务,避免线程创建过多导致上下文切换开销过大。 - maximumPoolSize:设置为
corePoolSize * 2
,在高并发时能适当增加线程数处理任务,但又不会过度创建线程。 - keepAliveTime:设置为10秒,当线程空闲超过这个时间,会被回收,释放资源。
- workQueue:使用
ArrayBlockingQueue
,并设置合理的容量,比如100。当任务数超过队列容量且线程数达到maximumPoolSize
时,新任务会根据饱和策略处理。
- corePoolSize:设置为
- 任务处理策略:
- 可以自定义饱和策略,比如实现
RejectedExecutionHandler
接口。例如,当任务被拒绝时,将任务放入一个持久化队列(如基于文件的队列),后续再进行处理。 - 示例代码:
class CustomRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 将任务放入持久化队列 // 这里只是示例,实际需实现具体的持久化逻辑 System.out.println("Task " + r + " rejected. Adding to persistent queue."); } } executor.setRejectedExecutionHandler(new CustomRejectedExecutionHandler());
- 可以自定义饱和策略,比如实现
结合内存管理机制进行性能调优
- 垃圾回收策略:
- 选择合适的垃圾回收器。在高并发场景下,
G1
垃圾回收器通常是一个不错的选择。它将堆内存划分为多个Region,采用并发标记 - 整理算法,能有效减少停顿时间。 - 可以通过设置JVM参数来启用
G1
垃圾回收器:-XX:+UseG1GC
。同时,可以根据应用场景调整G1
的一些关键参数,如-XX:G1HeapRegionSize
设置Region大小,-XX:MaxGCPauseMillis
设置最大停顿时间。
- 选择合适的垃圾回收器。在高并发场景下,
- 内存分配:
- 对象池:对于短生命周期任务中频繁创建和销毁的对象,可以使用对象池技术。例如,使用
Apache Commons Pool
库来管理对象池。对于数据库连接等资源,可以复用对象,减少频繁创建和销毁带来的内存开销。 - 直接内存:对于一些长生命周期任务,如果涉及到大量I/O操作,可以考虑使用直接内存(
ByteBuffer.allocateDirect()
)。直接内存不受JVM堆内存大小限制,能减少数据在堆内存和直接内存之间拷贝的开销,但要注意直接内存的释放,避免内存泄漏。
- 对象池:对于短生命周期任务中频繁创建和销毁的对象,可以使用对象池技术。例如,使用
- 避免内存溢出:
- 监控内存使用:使用
JMX
(Java Management Extensions)或VisualVM
等工具监控内存使用情况,实时了解堆内存、非堆内存的使用量,及时发现潜在的内存泄漏或内存增长过快问题。 - 调整堆内存大小:根据应用的实际需求,通过
-Xmx
和-Xms
参数合理调整JVM堆内存大小。比如,如果发现应用在运行过程中频繁进行垃圾回收且内存使用率较高,可以适当增大堆内存。但也不能无限增大,要考虑物理内存的限制。
- 监控内存使用:使用
- 保证系统高可用性:
- 任务隔离:将长生命周期任务和短生命周期任务进行隔离处理。可以使用不同的线程池分别处理这两类任务,避免长生命周期任务阻塞短生命周期任务的处理。例如,创建两个
ThreadPoolExecutor
,一个专门处理短生命周期任务,另一个处理长生命周期任务。 - 故障恢复:在任务执行过程中,对可能出现的异常进行捕获和处理。如果某个任务执行失败,记录相关日志,并提供重试机制。对于资源获取失败(如数据库连接获取失败),可以设置合理的重试次数和重试间隔,保证任务最终能够成功执行,提高系统的可用性。
- 任务隔离:将长生命周期任务和短生命周期任务进行隔离处理。可以使用不同的线程池分别处理这两类任务,避免长生命周期任务阻塞短生命周期任务的处理。例如,创建两个