面试题答案
一键面试线程复用策略
- 核心线程与非核心线程
- 使用
ThreadPoolExecutor
类,设置核心线程数corePoolSize
和最大线程数maximumPoolSize
。核心线程会一直存活,即使处于空闲状态也不会被销毁,而非核心线程在空闲时间超过keepAliveTime
后会被销毁。这样可以实现线程的复用,减少线程创建和销毁的开销。例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, // corePoolSize 20, // maximumPoolSize 60L, // keepAliveTime TimeUnit.SECONDS, new LinkedBlockingQueue<>());
- 使用
- 线程工厂
- 自定义
ThreadFactory
来创建线程,可对线程进行命名、设置优先级等操作,便于调试和管理。例如:
ThreadFactory threadFactory = new ThreadFactory() { private int counter = 0; @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("Custom - Thread - " + counter++); thread.setPriority(Thread.NORM_PRIORITY); return thread; } }; ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), threadFactory);
- 自定义
任务队列管理
- 队列类型选择
- 有界队列:如
ArrayBlockingQueue
,设置固定大小,能有效控制内存使用,防止任务堆积导致内存溢出。但如果队列已满且线程池达到最大线程数,新任务可能会被拒绝。适用于对资源使用严格控制的场景。例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
- 无界队列:如
LinkedBlockingQueue
,理论上可以容纳无限个任务。当线程池的核心线程都在忙碌时,新任务会进入队列等待,不会立即创建新的非核心线程,直到队列满了才会创建。适用于任务提交频率高但执行时间短的场景,不过要注意可能导致内存溢出问题。例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
- 优先队列:
PriorityBlockingQueue
,可根据任务的优先级进行排序,优先执行高优先级任务。需要任务实现Comparable
接口或传入Comparator
。例如:
PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<>(100, (r1, r2) -> { if (r1 instanceof PrioritizedTask && r2 instanceof PrioritizedTask) { return ((PrioritizedTask) r1).getPriority() - ((PrioritizedTask) r2).getPriority(); } return 0; }); ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, queue); class PrioritizedTask implements Runnable, Comparable<PrioritizedTask> { private int priority; @Override public int compareTo(PrioritizedTask o) { return this.priority - o.priority; } public int getPriority() { return priority; } @Override public void run() { // task logic } }
- 有界队列:如
- 队列监控与调整
- 可以通过继承
ThreadPoolExecutor
并重写beforeExecute
、afterExecute
和terminated
方法来监控任务队列的状态,如任务入队、出队等。例如:
class CustomThreadPoolExecutor extends ThreadPoolExecutor { @Override protected void beforeExecute(Thread t, Runnable r) { System.out.println("Task " + r + " is about to be executed by " + t); } @Override protected void afterExecute(Runnable r, Throwable t) { System.out.println("Task " + r + " has been executed, with exception: " + t); } @Override protected void terminated() { System.out.println("ThreadPool has been terminated"); } }
- 根据任务队列的长度和线程池的运行状态,动态调整队列大小或线程池的线程数量。例如,当队列长度超过一定阈值时,增加线程数量;当队列长度低于某个阈值且线程数大于核心线程数时,减少线程数量。
- 可以通过继承
资源监控与动态调整
- 资源监控
- 可以通过
ThreadPoolExecutor
提供的方法获取线程池的运行状态,如getActiveCount
获取当前活动线程数,getCompletedTaskCount
获取已完成的任务数,getTaskCount
获取已提交的任务总数等。例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); System.out.println("Active threads: " + executor.getActiveCount()); System.out.println("Completed tasks: " + executor.getCompletedTaskCount()); System.out.println("Total tasks: " + executor.getTaskCount());
- 使用
ScheduledExecutorService
定时任务来定期打印或记录线程池的状态信息,便于分析和优化。例如:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(() -> { System.out.println("Active threads: " + executor.getActiveCount()); System.out.println("Completed tasks: " + executor.getCompletedTaskCount()); System.out.println("Total tasks: " + executor.getTaskCount()); }, 0, 1, TimeUnit.MINUTES);
- 可以通过
- 动态调整
- 线程数量动态调整:通过
ThreadPoolExecutor
的setCorePoolSize
和setMaximumPoolSize
方法动态调整核心线程数和最大线程数。例如,当系统负载较低时,减少核心线程数;当负载较高时,增加最大线程数。
ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); // 动态增加核心线程数 executor.setCorePoolSize(15); // 动态减少最大线程数 executor.setMaximumPoolSize(18);
- 任务队列动态调整:如果使用的是有界队列,可以通过反射等方式动态修改队列的容量。例如,对于
ArrayBlockingQueue
,可以获取其内部数组并重新创建一个更大或更小容量的队列来替换原队列。不过这种方法较为复杂且需要谨慎使用,因为可能涉及到多线程并发访问队列的问题。
- 线程数量动态调整:通过
并发安全问题及解决方案
- 任务提交与队列操作并发问题
- 问题:当多个线程同时向线程池提交任务时,可能会出现任务丢失或队列操作不一致的情况。
- 解决方案:
ThreadPoolExecutor
内部的任务队列(如ArrayBlockingQueue
、LinkedBlockingQueue
等)本身是线程安全的,通过使用锁机制(如ReentrantLock
等)来保证多线程下的安全操作。在自定义线程池时,如果使用自定义队列,需要确保队列实现是线程安全的,或者在队列操作方法上添加合适的同步机制,如synchronized
关键字。
- 线程状态更新并发问题
- 问题:在多线程环境下,线程池中的线程状态(如空闲、忙碌等)更新可能出现竞争条件,导致状态不准确。
- 解决方案:
ThreadPoolExecutor
使用AtomicInteger
类型的变量来表示线程池的状态,利用其原子操作特性保证状态更新的原子性和线程安全。在自定义线程池时,如果需要自定义线程状态管理,也应使用类似的原子类或合适的同步机制来确保状态更新的正确性。
- 资源监控并发问题
- 问题:在获取线程池的运行状态信息(如活动线程数、任务完成数等)时,由于这些信息可能在不同线程中动态变化,可能获取到不准确的数据。
- 解决方案:
ThreadPoolExecutor
提供的获取状态信息的方法(如getActiveCount
等)是线程安全的,其内部通过对相关状态变量的原子操作或合适的同步机制来保证数据的准确性。在自定义监控逻辑时,如果涉及对共享状态变量的读取,应确保使用线程安全的方式进行操作,如使用Atomic
类型变量或同步块。