MST

星途 面试题库

面试题:Java线程池在处理串行执行任务时如何避免资源浪费

当需要在线程池中执行一系列串行任务时,可能会出现资源浪费的情况,比如线程的频繁创建与销毁。请详细描述如何通过合理配置线程池参数以及设计任务执行逻辑,来有效避免这种资源浪费现象,同时说明涉及到的Java线程池相关的关键知识点。
43.7万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

避免资源浪费的方法

  1. 合理配置线程池参数
    • 核心线程数(corePoolSize):设置为能够满足应用程序常规负载下所需的线程数量。例如,如果应用程序通常会同时处理5个串行任务,并且每个任务执行时间较长,那么可以将corePoolSize设置为5。这样,线程池会始终保留这5个线程,避免频繁创建新线程。
    • 最大线程数(maximumPoolSize):在考虑到突发负载的情况下,设置合理的最大线程数。比如,应用程序偶尔可能会有10个串行任务同时到达,那么maximumPoolSize可以设置为10。注意,这个值不宜过大,否则可能会导致过多线程竞争资源,反而降低性能。
    • 存活时间(keepAliveTime):对于非核心线程,如果在keepAliveTime时间内没有任务可执行,就会被销毁。在串行任务场景下,如果希望线程在任务执行完后不立即销毁,而是等待后续可能的串行任务,可以将keepAliveTime设置为一个较大的值,例如Long.MAX_VALUE。同时,需要将allowCoreThreadTimeOut设置为true,这样核心线程在空闲时也会遵循这个存活时间规则,避免核心线程频繁销毁与创建。
    • 任务队列(workQueue):选择合适的任务队列。对于串行任务,LinkedBlockingQueue是一个不错的选择。它是一个无界队列,可以缓存大量任务,当核心线程都在忙碌时,新的任务会被放入队列中等待执行,而不是立即创建新的非核心线程,从而减少线程的不必要创建。
  2. 设计任务执行逻辑
    • 封装任务:将串行任务封装成一个大的任务单元,例如可以使用CompletableFuture来组合多个串行的子任务。假设我们有三个串行任务task1task2task3,可以这样封装:
CompletableFuture<Void> combinedTask = CompletableFuture.runAsync(() -> task1())
   .thenRun(() -> task2())
   .thenRun(() -> task3());
- **使用`ExecutorService`提交任务**:通过`ExecutorService`将封装后的任务提交到线程池执行。
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.submit(combinedTask);

Java线程池相关关键知识点

  1. 线程池的工作原理:线程池维护着一个线程队列和一个任务队列。当有任务提交时,首先会检查核心线程是否都在忙碌,如果有空闲核心线程,则分配任务给它执行;如果核心线程都在忙碌,则任务会被放入任务队列;当任务队列也满了,且当前线程数小于最大线程数时,会创建新的非核心线程来执行任务;如果线程数达到最大线程数且任务队列也满了,根据不同的拒绝策略处理新任务,例如直接抛出异常(AbortPolicy)、将任务回退给调用者(CallerRunsPolicy)等。
  2. 线程池的类型
    • FixedThreadPool:固定大小的线程池,核心线程数和最大线程数相等,任务队列是LinkedBlockingQueue。适用于任务数量比较稳定,且执行时间较长的场景,避免了线程的频繁创建与销毁。
    • CachedThreadPool:可缓存线程池,核心线程数为0,最大线程数为Integer.MAX_VALUE,任务队列是SynchronousQueue。它适用于执行时间短且任务数量波动较大的场景,但在串行任务场景下可能会导致大量线程的频繁创建与销毁,不适合本题需求。
    • SingleThreadExecutor:单线程的线程池,只有一个核心线程,任务队列是LinkedBlockingQueue。它保证所有任务按照顺序执行,类似串行任务,但只有一个线程,在处理多个串行任务时效率可能较低,不过能避免线程频繁创建销毁。
    • ScheduledThreadPool:用于定时和周期性任务的线程池,在串行任务场景下,如果任务有定时执行需求,可以考虑使用。
  3. 拒绝策略
    • AbortPolicy:默认策略,当任务无法执行时抛出RejectedExecutionException
    • CallerRunsPolicy:将任务回退给调用者线程执行,这样可以减少新线程的创建,适用于任务提交速度过快的场景。
    • DiscardPolicy:直接丢弃无法执行的任务,不抛出异常。
    • DiscardOldestPolicy:丢弃任务队列中最老的任务,然后尝试提交新任务。