MST
星途 面试题库

面试题:Java线程池在高并发场景下如何避免任务饥饿现象

在高并发环境中,线程池可能会出现任务饥饿的情况,即某些任务长时间得不到执行。请阐述在Java线程池中,可能导致任务饥饿的原因有哪些,并且说明如何通过合理设置线程池参数、任务队列策略或采用特定的调度算法来避免这种现象,同时给出相关的代码示例。
13.2万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

可能导致任务饥饿的原因

  1. 线程池核心线程数过小:核心线程数设置过少,当任务量较大时,大部分任务只能在队列中等待,而核心线程一直处于忙碌状态,新提交的任务长时间得不到执行。
  2. 任务队列容量过大:如果任务队列容量设置过大,大量任务堆积在队列中,可能会导致优先级高或者需要及时处理的任务长时间等待。
  3. 非公平调度:默认情况下,线程池采用非公平调度算法,新提交的任务可能会抢占正在等待执行任务的线程,导致部分任务等待时间过长。
  4. 任务类型不均:如果线程池中既有耗时短的任务,又有耗时长的任务,并且没有进行合理区分调度,耗时短的任务频繁执行,而耗时长的任务容易被积压。

避免任务饥饿的方法

  1. 合理设置线程池参数
    • 核心线程数:根据任务的性质和预估的并发量来合理设置核心线程数。对于CPU密集型任务,核心线程数可以设置为CPU核心数 + 1;对于I/O密集型任务,可以适当增加核心线程数,比如CPU核心数 * 2。
    • 最大线程数:最大线程数要根据系统资源(如内存、CPU等)来设置,避免创建过多线程导致系统资源耗尽。
    • 队列容量:根据任务的特点设置合适的队列容量。如果任务对及时性要求较高,队列容量不宜过大。
  2. 任务队列策略
    • 使用优先级队列:自定义任务类实现Comparable接口,重写compareTo方法定义任务优先级。在创建线程池时,使用PriorityBlockingQueue作为任务队列。这样优先级高的任务会优先被执行。
  3. 采用特定的调度算法
    • 公平调度:可以通过设置RejectedExecutionHandler,并在其中实现公平调度逻辑。比如使用ThreadPoolExecutor.CallerRunsPolicy,当线程池饱和时,由提交任务的线程自己来执行任务,从而保证任务的公平性。

代码示例

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class PriorityTask implements Runnable, Comparable<PriorityTask> {
    private int priority;
    private String taskName;

    public PriorityTask(int priority, String taskName) {
        this.priority = priority;
        this.taskName = taskName;
    }

    @Override
    public void run() {
        System.out.println("Executing task: " + taskName + " with priority: " + priority);
    }

    @Override
    public int compareTo(PriorityTask other) {
        return Integer.compare(this.priority, other.priority);
    }
}

public class ThreadPoolTaskStarvationExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> taskQueue = new PriorityBlockingQueue<>();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,
                4,
                10,
                TimeUnit.SECONDS,
                taskQueue,
                new ThreadPoolExecutor.CallerRunsPolicy());

        executor.submit(new PriorityTask(3, "Task C"));
        executor.submit(new PriorityTask(1, "Task A"));
        executor.submit(new PriorityTask(2, "Task B"));

        executor.shutdown();
    }
}

在上述代码中:

  1. PriorityTask类实现了RunnableComparable接口,通过compareTo方法定义了任务的优先级。
  2. 使用PriorityBlockingQueue作为线程池的任务队列,确保高优先级任务优先执行。
  3. 设置ThreadPoolExecutor.CallerRunsPolicy作为拒绝策略,在一定程度上保证公平性。