• Java线程池队列吃的太饱,撑着了咋整?java 队列过大导致内存溢出


    Java的Executors框架提供的定长线程池内部默认使用LinkedBlockingQueue作为任务的容器,这个队列是没有限定大小的,可以无限向里面submit任务。

    当线程池处理的太慢的时候,队列里的内容会积累,积累到一定程度就会内存溢出。即使没有内存溢出,队列的延迟势必会变大,而且如果进程突然遇到退出信号,队列里的消息还没有被处理就被丢弃了,那必然会对系统的消息可靠性造成重大影响。

    那如何解决线程池的过饱问题呢?从队列入手,无外乎两种方法

    增加消费者,增加消费者处理效率

    限制生产者生产速度

    增加消费者就是增加线程池大小,增加消费者处理效率就是优化逻辑处理。但是如果遇到了IO瓶颈,消费者处理的效率完全取决于IO效率,在消费能力上已经优化到了极限还是处理不过来怎么办?或者系统突然遇到用户高峰,我们所配置的线程池大小不够用怎么办?

    这时候我们就只能从生产者入手,限制生产者的生产速度。那如何限制呢?

    LinkedBlockingQueue提供了capacity参数可以限制队列的大小,当队列元素达到上线的时候,生产者线程会阻塞住,直到队列被消费者消费到有空槽的时候才会继续下去。这里似乎只要给队列设置一个大小就ok了。

    但是实际情况并不是我们所想的那样。

    翻开源码可以发现生产者向队列里塞任务用的方法是workQueue.offer(),这个方法在遇到队列满时是不会阻塞的,而是直接返回一个false,表示抛弃了这个任务。然后生产者调用reject方法,进入拒绝处理逻辑。

    接下来我们看看这个reject方法到底干了什么

    我们看到JDK默认提供了4中拒绝策略的实现。

    AbortPolicy 默认策略,抛出RejectedExecutionException异常

    CallerRunsPolicy 让任务在生产者线程里执行,这样可以降低生产者的生产速度也不会将生产者的线程堵住

    DiscardPolicy 直接抛弃任务,不抛异常

    DiscardOldestPolicy 直接抛弃旧任务,不抛异常

    一般比较常用的是CallerRunPolicy,比较优雅的解决了过饱问题。如果你觉得这种方式不那么优雅的话,还可以使用下面的几种方式。这几种方式都是通过处理RejectExecution来实现生产者的阻塞的目的。

    这种方案是使用put方法会阻塞生产者线程的原理达到了目的。

    这种方案显而易见,用sleep达到了阻塞的目的。

    这种方案是通过信号量的大小都限制队列的大小,也不需要特别限定executor队列大小了

    同样的原理还可以使用wait/notifyAll机制来达到一样的目的。

  • 相关阅读:
    excel 2003系列
    DataTab转换XML XML转换DataTable 的类[转]
    全角转半角与半角转全角
    Day2
    Day6 && Day7图论
    Day1
    Android为何以及如何保存Fragment实例
    Android应用的本地化及知识拓展之配置修饰符
    Leetcode NO.136 Single Number 只出现一次的数字
    经典排序算法(四) —— Quick Sort 快速排序
  • 原文地址:https://www.cnblogs.com/panchanggui/p/9293169.html
Copyright © 2020-2023  润新知