Java 线程池内存队列满了怎么办?

本文探讨了Java线程池内存队列满的问题,分析了其原因和影响,并提供了增加队列容量、动态调整线程池、使用阻塞策略、拒绝任务和监控预警等解决方案。作者还给出了一个自定义RejectedExecutionHandler的演示示例。
摘要由CSDN通过智能技术生成

Java 线程池内存队列满了怎么办

0 概述

  • Java 线程池是处理并发编程的重要工具,而内存队列作为线程池的核心组件之一,用于存储待处理的任务。当内存队列满时,意味着线程池已达到其处理能力的上限。如何处理这种情况是确保系统稳定和高效的关键。

1 内存队列满的原因及影响

  • 原因分析:
    • 任务过多:提交给线程池的任务量超过了线程池的处理能力。
    • 线程池配置不当:核心线程数、最大线程数、队列大小等配置不匹配实际需求。
    • 任务执行时间长:某些任务执行时间过长,导致队列迅速填满。
  • 影响:
    • 系统性能下降:新任务无法及时处理,系统吞吐量降低。
    • 资源耗尽:长时间满队列可能导致系统资源耗尽,如内存溢出。
    • 系统稳定性下降:可能导致程序崩溃或不稳定状态。

2 一些解决方案

  • 增加队列容量: 如果队列满了,一个简单的解决方案是增加队列的容量。这可以通过在创建线程池时设置一个更大的队列来实现。

    // 创建一个固定大小为10的线程池,队列容量为1000
    ExecutorService executor = Executors.newFixedThreadPool(10, 1000); 
    
  • 动态调整线程池参数: 当队列快满时,可以动态地增加线程池的大小。这可以通过实现自定义的 RejectedExecutionHandler 来实现

    ThreadPoolExecutor executor = new ThreadPoolExecutor(...);  
    executor.setRejectedExecutionHandler((r, executor) -> {  
        // 处理被拒绝的任务,例如重新提交任务到队列中等待执行  
    });
    
  • 使用阻塞策略: 当队列满了,但任务仍然在不断提交时,可以使用阻塞策略。可以阻塞提交任务的线程,直到队列中有空间为止

    BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(1000);  
    executor.submit(() -> {  
        try {  
            queue.take(); // 阻塞直到队列中有空间为止  
        } catch (InterruptedException e) {  
            Thread.currentThread().interrupt();  
        }  
    });
    
  • 拒绝任务: 当队列满了并且无法扩大时,可以选择优雅地拒绝新任务。也是通过实现自定义的 RejectedExecutionHandler 来实现

    ThreadPoolExecutor executor = new ThreadPoolExecutor(...);  
    executor.setRejectedExecutionHandler((r, executor) -> {  
        // 处理被拒绝的任务,例如记录日志、返回一个默认值或者抛出一个异常等  
    });
    
  • 监控和预警: 通过监控工具来定期检查线程池的状态,当队列使用率接近或达到警戒线时,发出预警。这可以帮助及时发现和解决问题,可以选择 JMX 来监控线程池的状态

    ThreadPoolExecutor executor = new ThreadPoolExecutor(...);  
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();  
    ObjectName name = new ObjectName("com.example:type=ThreadPoolExecutor,name=myExecutor");  
    StandardMBean mbean = new StandardMBean(executor, StandardMBean.class);  
    mbs.registerMBean(mbean, name); // 注册MBean以供监控
    

3 演示 Demo

  • 下面将对自定义拒绝策略进行演示

    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.RejectedExecutionHandler;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @Author Jasper
     * @Time 2024/2/3
     * @公众号:EzCoding
     */
    public class RejectDemo {
    
        private static final int CAPACITY = 5;        // 初始队列容量
        private static final int MAX_TRY_COUNT = 3;   // 最大重试次数
        private static final LinkedBlockingQueue<Runnable> queue;
        private static final ThreadPoolExecutor executor;
        // 简单的重新入队策略
        private static final RejectedExecutionHandler handler = (r, executor) -> {
            if (!executor.isShutdown()) {
                try {
                    // 等待一段时间后重新提交任务
                    System.out.println("任务 " + r.toString() + " 被拒绝,准备重新提交");
                    Thread.sleep(1000); // 等待1秒
                    executor.getQueue().put(r); // 重新提交任务到队列
                } catch (InterruptedException e) {
                    // 如果等待被中断,则不再重新提交
                    Thread.currentThread().interrupt();
                }
            }
        };
    
        // 包含重试次数的重新入队策略
        private static final RejectedExecutionHandler retryHandler = (r, executor) -> {
            ConcurrentHashMap<Runnable, AtomicInteger> map = new ConcurrentHashMap<>();
            if (!executor.isShutdown()) {
                AtomicInteger count = map.computeIfAbsent(r, k -> new AtomicInteger(0));
                int tryCount = count.incrementAndGet();
                if (tryCount <= MAX_TRY_COUNT) {
                    try {
                        System.out.println("任务 " + r + " 被拒绝,第 " + tryCount + " 次重试");
                        Thread.sleep(1000); // 等待1秒
                        executor.getQueue().put(r); // 重新提交任务到队列
                    } catch (InterruptedException e) {
                        // 如果等待被中断,则不再重新提交
                        Thread.currentThread().interrupt();
                    }
                } else {
                    System.out.println("任务 " + r.toString() + " 达到最大重试次数 " + MAX_TRY_COUNT + ",放弃该任务");
                    // 可以选择抛出异常、记录日志或其他处理方式...
                }
            }
        };
    
        static {
            queue = new LinkedBlockingQueue<>(CAPACITY);
            executor = new ThreadPoolExecutor(
                    1, // 核心线程数
                    2,  // 最大线程数
                    60,
                    TimeUnit.SECONDS,
                    queue,   // 阻塞队列
                    r -> {   // 线程工厂
                        AtomicInteger threadNumber = new AtomicInteger(1);
                        Thread thread = new Thread(r);
                        thread.setName("Thread-" + threadNumber.getAndIncrement());
                        thread.setDaemon(false); // 设置为非守护线程,以避免程序退出时中断任务
                        return thread;
                    },
                    retryHandler
            );
    
        }
    
        public static void doTask(int taskId) {
            try {
                System.out.println("正在执行任务 " + taskId);
                Thread.sleep(500); // 模拟任务执行时间
                System.out.println("任务 " + taskId + " 执行完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                final int taskId = i;
                executor.submit(() -> doTask(taskId));
            }
            // 为了防止主线程退出,导致线程池中的线程被中断,可以等待线程池中的所有任务执行完成
            try {
                // 等待所有任务执行完成,包括队列中的任务
                executor.shutdown();
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    executor.shutdownNow();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
    }
    
    /**
    * 输出:
    * 	正在执行任务 6
    *	正在执行任务 0
    *	任务 java.util.concurrent.FutureTask@68de145[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@4b85612c[Wrapped task = top.ezjava.java17demo.thread.task.RejectDemo$$Lambda$18/0x0000027281003218@277050dc]] 被拒绝,第 1 次重试
    *	任务 6 执行完成
    *	正在执行任务 1
    *	任务 0 执行完成
    *	正在执行任务 2
    *	任务 1 执行完成
    *	正在执行任务 3
    *	任务 2 执行完成
    *	正在执行任务 4
    *	任务 3 执行完成
    *	正在执行任务 5
    *	任务 4 执行完成
    *	正在执行任务 7
    *	任务 5 执行完成
    *	正在执行任务 8
    *	任务 7 执行完成
    *	正在执行任务 9
    *	任务 9 执行完成
    *	任务 8 执行完成
    */
    
  • 以上就是 Java 线程池内存队列满了如何处理的全部内容了,不知道你们在平时是怎么处理队列打满问题的呢,欢迎在评论区讨论~

  • 创作不易,感谢阅读,若遇到问题,可以关注此微信gzh:EzCoding 留言反馈,希望能够帮助到您

Java中,线程池和阻塞队列(BlockingQueue)的选择与设置是非常关键的,因为它们直接影响了系统的并发性能和资源管理。线程池中的阻塞队列用于存储任务,当线程池中的工作线程空闲时,会从队列中取出任务执行;反之,如果任务队列了,新提交的任务会被阻塞,直到队列中有空间。 决定队列大小的因素通常包括: 1. **系统负载**:考虑应用程序的平均请求速率以及峰值负载。如果预计请求量波动较大,可能需要较大的缓冲能力来应对高峰期。 2. **硬件限制**:比如内存大小。队列过大会消耗过多内存,影响其他部分的性能。队列过小可能导致频繁地创建和销毁线程,增加上下文切换成本。 3. **任务类型**:对于I/O密集型任务,队列可以适当大一些,因为它不需要立即处理结果;而对于计算密集型任务,队列可能需要更小,以免CPU等待IO完成。 4. **超时策略**:如果你希望在队列时设定一个时间限制(如生产者阻塞的时间),那么队列大小应该足够容纳这个超时时间内的任务。 常用的Java阻塞队列有`ArrayBlockingQueue`、`LinkedBlockingQueue`、`PriorityBlockingQueue`等,它们的配置参数`容量`就是指队列的最大元素数量。一般来说,初始容量可以设置为核心线程数的1.5到3倍,然后根据实际情况调整。同时,也可以设置无界队列(如`LinkedList`或自定义无限大小的队列),但这样可能会导致大量内存占用。 设置合适的队列大小需要进行实际监控和测试,找到一个既足吞吐需求又能保证系统稳定性的平衡点。如果你提供具体的应用场景或数据,我可以给出更具体的建议。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值