线程池中任务堆积与饥饿死锁问题

一、Bug 场景

在一个电商后台系统中,使用线程池来处理订单处理、库存更新、物流通知等多种异步任务。随着业务量的增长,系统逐渐出现响应变慢的情况,部分任务甚至长时间得不到执行,最终导致一些关键业务流程受阻。

二、代码示例

任务类

java 复制代码
public class BusinessTask implements Runnable {
    private final String taskName;

    public BusinessTask(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        try {
            // 模拟业务处理
            Thread.sleep(1000);
            System.out.println(taskName + " 任务完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

线程池配置与任务提交(有缺陷)

java 复制代码
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class EcommerceSystem {
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 20;
    private static final long KEEP_ALIVE_TIME = 1L;

    public static void main(String[] args) {
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                taskQueue);

        // 模拟提交大量任务
        for (int i = 0; i < 50; i++) {
            String taskName = "任务 " + i;
            executor.submit(new BusinessTask(taskName));
        }

        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("线程池未正常关闭");
                }
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

三、问题描述

  1. 预期行为:线程池能够高效处理提交的任务,所有任务都能在合理时间内完成,系统保持稳定运行。

  2. 实际行为

    • 任务堆积:随着任务不断提交,任务队列逐渐被填满,新任务只能等待队列有空位或者线程池创建新线程(达到最大线程数后也只能等待)。这是因为线程池的核心线程数和最大线程数设置相对业务量过小,并且任务执行时间较长,导致任务处理速度跟不上提交速度。
    • 饥饿死锁:在任务队列中可能存在优先级不同的任务,例如订单处理任务优先级较高,物流通知任务优先级较低。如果高优先级任务持续提交,线程池中的线程会一直忙于处理高优先级任务,低优先级任务可能长时间得不到执行,出现饥饿现象。极端情况下,可能导致低优先级任务永远无法执行,形成一种类似死锁的状态。

四、解决方案

  1. 优化线程池参数:根据实际业务负载和任务特性,合理调整线程池参数。例如,如果任务执行时间较短且数量较多,可以适当增加核心线程数和最大线程数;如果任务执行时间较长,可以扩大任务队列容量。
java 复制代码
private static final int CORE_POOL_SIZE = 10;
private static final int MAX_POOL_SIZE = 20;
private static final int QUEUE_CAPACITY = 50;
  1. 使用优先级队列和优先级线程池 :对任务进行优先级划分,使用 PriorityBlockingQueue 作为任务队列,并自定义 PriorityThreadPoolExecutor 来处理不同优先级的任务。
java 复制代码
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class PriorityBusinessTask implements Runnable, Comparable<PriorityBusinessTask> {
    private final String taskName;
    private final int priority;

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

    @Override
    public void run() {
        try {
            // 模拟业务处理
            Thread.sleep(1000);
            System.out.println(taskName + " 任务完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

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

public class PriorityThreadPoolExecutor extends ThreadPoolExecutor {
    public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        if (r instanceof PriorityBusinessTask) {
            System.out.println("开始执行优先级任务: " + ((PriorityBusinessTask) r).taskName);
        }
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (r instanceof PriorityBusinessTask) {
            System.out.println("完成优先级任务: " + ((PriorityBusinessTask) r).taskName);
        }
    }
}

public class EcommerceSystem {
    private static final int CORE_POOL_SIZE = 10;
    private static final int MAX_POOL_SIZE = 20;
    private static final int QUEUE_CAPACITY = 50;
    private static final long KEEP_ALIVE_TIME = 1L;

    public static void main(String[] args) {
        BlockingQueue<Runnable> taskQueue = new PriorityBlockingQueue<>(QUEUE_CAPACITY);
        PriorityThreadPoolExecutor executor = new PriorityThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                taskQueue);

        // 模拟提交不同优先级任务
        executor.submit(new PriorityBusinessTask("高优先级任务 1", 1));
        executor.submit(new PriorityBusinessTask("低优先级任务 1", 3));
        executor.submit(new PriorityBusinessTask("高优先级任务 2", 1));
        executor.submit(new PriorityBusinessTask("低优先级任务 2", 3));

        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("线程池未正常关闭");
                }
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
  1. 动态调整线程池:根据系统运行时的任务队列长度、线程池活跃线程数等指标,动态调整线程池的参数,以适应业务量的变化。可以使用定时任务定期检查并调整线程池大小。
java 复制代码
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class EcommerceSystem {
    private static final int INITIAL_CORE_POOL_SIZE = 5;
    private static final int INITIAL_MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 20;
    private static final long KEEP_ALIVE_TIME = 1L;

    public static void main(String[] args) {
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                INITIAL_CORE_POOL_SIZE,
                INITIAL_MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                taskQueue);

        ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
        scheduler.scheduleAtFixedRate(() -> {
            int queueSize = taskQueue.size();
            int activeCount = executor.getActiveCount();
            if (queueSize > QUEUE_CAPACITY * 0.8 && activeCount < INITIAL_MAX_POOL_SIZE) {
                executor.setCorePoolSize(executor.getCorePoolSize() + 1);
                executor.setMaximumPoolSize(executor.getMaximumPoolSize() + 1);
            } else if (queueSize < QUEUE_CAPACITY * 0.2 && activeCount > INITIAL_CORE_POOL_SIZE) {
                executor.setCorePoolSize(executor.getCorePoolSize() - 1);
                executor.setMaximumPoolSize(executor.getMaximumPoolSize() - 1);
            }
        }, 0, 1, TimeUnit.MINUTES);

        // 模拟提交大量任务
        for (int i = 0; i < 50; i++) {
            String taskName = "任务 " + i;
            executor.submit(new BusinessTask(taskName));
        }

        executor.shutdown();
        scheduler.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("线程池未正常关闭");
                }
            }
            if (!scheduler.awaitTermination(1, TimeUnit.MINUTES)) {
                scheduler.shutdownNow();
                if (!scheduler.awaitTermination(1, TimeUnit.MINUTES)) {
                    System.err.println("调度器未正常关闭");
                }
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
相关推荐
nbsaas-boot17 小时前
Go vs Java 的三阶段切换路线图
java·开发语言·golang
毕设源码-钟学长17 小时前
【开题答辩全过程】以 基于Java的慕课点评网站为例,包含答辩的问题和答案
java·开发语言
小北方城市网17 小时前
分布式锁实战指南:从选型到落地,避开 90% 的坑
java·数据库·redis·分布式·python·缓存
深圳佛手17 小时前
使用java,怎么样高效地读取一个大文件(10g以上)?
java·开发语言
sheji341617 小时前
【开题答辩全过程】以 景点移动导游系统的设计与实现为例,包含答辩的问题和答案
java
毕设源码-赖学姐17 小时前
【开题答辩全过程】以 高校失物招领信息管理系统的设计与开发为例,包含答辩的问题和答案
java
xiaolyuh12317 小时前
【XXL-JOB】 GLUE模式 底层实现原理
java·开发语言·前端·python·xxl-job
ohoy17 小时前
RedisTemplate 使用之Zset
java·开发语言·redis
独断万古他化17 小时前
【Spring 核心: IoC&DI】从原理到注解使用、注入方式全攻略
java·后端·spring·java-ee
梵得儿SHI17 小时前
(第四篇)Spring AI 核心技术攻坚:多轮对话与记忆机制,打造有上下文的 AI
java·人工智能·spring·springai生态·上下文丢失问题·三类记忆·智能客服实战案