创建ThreadPoolExecutor使用无界任务队列导致内存飙高问题的解决

本文探讨了使用juc包创建线程池时的问题与优化方案,指出无界队列可能导致内存溢出,建议使用有界队列配合CallerRunsPolicy策略避免任务丢失。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.问题:

在使用juc包的ThreadPoolExecutor创建线程池时候,可以选择不同类型的创建方法,使用Executors提供的newFixedThreadPool()方法时候,源码是这样的:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

使用的是无界队列,如果并发任务量巨大,任务逻辑比较耗时,未来得及处理的任务会大量堆积在队列里,导致内存急速飙高,可能导致程序挂掉。

2.解决方法

线程任务使用有界阻塞队列,队列满了就暂停放入线程任务;但是这样拒绝策略要设置好,因为默认的是超过队列长度就丢弃的策略AbortPolicy,这样不行,应该使用CallerRunsPolicy策略,直接让调用者执行任务。

示例:

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadPoolExecutorTest {

    public static void main(String[] args) {
        ExecutorService executor = new ThreadPoolExecutor(5, 20, 0,
                TimeUnit.SECONDS,
            //1.如果使用无界队列,大量提交任务会堆积,造成内存占用太高
//                new LinkedBlockingQueue<Runnable>(),
                //2.应该使用有界队列
                new LinkedBlockingQueue<Runnable>(50),
                //2.加上调用线程任务本身去run
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        AtomicInteger count = new AtomicInteger();

        for (int i = 0; i < 100; i++) {
            executor.execute(() -> {
//                int andIncrement = count.getAndIncrement();
                System.out.println("num=" + count.incrementAndGet());

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    }
}

 以上线程池,使用第2种有界队列的话,长度设为50,并且最后一个构造参数不写,默认就是AbortPolicy,会造成部分任务被丢弃,抛出异常:

java.util.concurrent.RejectedExecutionException: Task cn.demo.configClient.ThreadPoolExecutorTest$$Lambda$1/396180261@5910e440 rejected from java.util.concurrent.ThreadPoolExecutor@6267c3bb[Running, pool size = 20, active threads = 20, queued tasks = 50, completed tasks = 0]
num=9
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)

 

<think>嗯,用户现在遇到了环形队列内存分配失败的问题,需要解决方案。首先,我得回忆一下环形队列的基本结构和常见的内存分配问题。环形队列通常使用数组或预先分配的内存块来实现,当队列时无法继续插入元素,这时候可能会报内存分配失败的错误。不过,这里的内存分配失败可能指的是动态内存分配的问题,比如在运行时无法申请到足够的内存,或者管理不当导致碎片化等问题。 根据用户提供的引用内容,特别是引用[1]中提到的动态内存分配方法,比如伙伴系统、segregated lists等,可能需要考虑内存分配策略是否合适。例如,如果使用伙伴系统,可能会因为频繁分配和释放不同大小的内存导致碎片,进而无法足环形队列内存需求。或者,如果使用的是固定大小的内存块,可能因为队列增长超过预分配的大小而失败。 另外,引用[3]提到环形队列的操作,比如删除元素时检查队列是否为空,更新head和count。这可能涉及到多线程环境下的同步问题,如果同步机制不当,可能导致内存分配或释放的错误,比如竞态条件使得队列状态不一致,从而引发分配失败。 接下来,我需要考虑用户可能的情况。用户提到的是环形队列内存分配失败,可能是在动态扩展队列时遇到了问题。比如,当队列时尝试扩展内存但失败,或者初始分配的内存不足。解决方案可能包括预分配足够内存使用合适的内存分配算法、检查内存泄漏、优化同步机制等。 还需要参考用户提供的引用中的方法,比如是否可以采用segregated lists来管理固定大小的内存块,避免碎片。或者使用伙伴系统来更效地合并空闲块。此外,程序计数器的问题虽然不直接相关,但需要考虑是否有其他资源竞争导致内存分配失败。 可能的解决方案步骤:1. 检查预分配内存是否足够,考虑静态分配;2. 动态扩展时使用效算法如伙伴系统;3. 确保正确的同步机制,避免多线程问题;4. 检测内存泄漏,确保释放操作正确;5. 优化数据结构,如使用链表减少碎片;6. 监控资源使用情况,及时处理异常。 需要确保这些方案结合用户的具体情况,比如环形队列的实现方式、是否多线程、使用内存分配方法等。可能需要用户提供更多细节,但根据现有信息,给出通用的解决策略。</think>针对环形队列内存分配失败的问题,以下是分步解决方案及技术要点: ### 一、问题定位与分析 1. **确认失败场景** - 静态分配场景:检查预分配内存是否溢出,例如环形队列容量$N$是否过小 - 动态分配场景:验证内存扩展逻辑是否触发失败(例如扩展时系统无可用连续内存)[^1] 2. **内存碎片检测** 使用工具(如Valgrind)分析内存碎片化程度,尤其是频繁分配/释放后剩余内存是否无法足环形队列扩展需求 $$ \text{碎片率} = \frac{\text{不可用碎片内存总量}}{\text{总空闲内存}} \times 100\% $$ ### 二、核心解决方案 #### 方案1:预分配固定内存(推荐) ```c #define QUEUE_SIZE 1024 // 根据业务需求设定上限 typedef struct { int buffer[QUEUE_SIZE]; // 静态分配内存 int head, tail, count; } CircularQueue; ``` - **优势**:消除运行时内存分配失败风险 - **限制**:需提前评估最大容量需求[^3] #### 方案2:动态内存管理优化 1. **采用伙伴系统(Buddy System)** 通过合并相邻空闲块减少碎片,适合需要动态扩展的场景: ```python class BuddySystem: def allocate(self, size): # 查找足条件的最小内存块并分割 pass def deallocate(self, block): # 释放内存并尝试合并相邻块 pass ``` - **效果**:内存利用率提升约30%[^1] 2. **使用segregated lists管理** 按固定大小划分内存池,例如为环形队列单独分配专用内存池: ```c #define BLOCK_SIZE 64 // 对齐到CPU缓存行大小 void* queue_mempool = malloc(BLOCK_SIZE * 1000); // 预分配内存池 ``` #### 方案3:同步机制强化 在多线程场景下,采用双重检测锁避免竞争: ```c pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void enqueue(CircularQueue* q, int item) { pthread_mutex_lock(&lock); if (q->count >= QUEUE_SIZE) { // 处理溢出或扩展内存 } // 入队操作 pthread_mutex_unlock(&lock); } ``` ### 三、深度优化策略 1. **内存泄漏检测** 实现引用计数机制,确保每次`malloc`都有对应的`free`: ```c #ifdef DEBUG size_t alloc_count = 0; void* debug_malloc(size_t size) { alloc_count++; return malloc(size); } #endif ``` 2. **分页式内存管理** 将环形队列划分为多个内存页(例如4KB单位),通过页表管理离散物理内存: ``` Page Table Entry: | 物理页地址 | 是否连续 | 下一页指针 | ``` ### 四、验证与测试 1. 压力测试:持续运行队列操作$10^6$次,监控内存增长曲线 2. Valgrind检测:执行`valgrind --leak-check=full ./your_program` 3. 性能对比:记录优化前后的内存分配延迟(建议使用`<chrono>`精度计时)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值