## 线程池使用需要注意的问题:

2020-08-09  本文已影响0人  码而优则仕

线程池使用需要注意的问题:

Java 中的 Executors 类定义了一些快捷的工具方法,来帮助我们快速创建线程池。《阿里巴巴 Java 开发手册》中提到,禁止使用这些方法来创建线程池,而应该手动 new ThreadPoolExecutor 来创建线程池。这一条规则的背后,是大量血淋淋的生产事故,最典型的就是 newFixedThreadPool 和 newCachedThreadPool,可能因为资源耗尽导致 OOM 问题。

虽然使用 newFixedThreadPool 可以把工作线程控制在固定的数量上,但任务队列是无界的。如果任务较多并且执行较慢的话,队列可能会快速积压,撑爆内存导致 OOM。

Executors.newFixedThreadPool(1);
//核心线程和最大线程数量都设置为1 ,keepAliveTime 0,工作队列使用LinkedBlockingQueue,容量为Integer.MAX_VALUE即相当于无限大。不设置线程工程和拒绝策略,使用默认的。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
 public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

翻看 newCachedThreadPool 的源码可以看到,这种线程池的最大线程数是 Integer.MAX_VALUE,可以认为是没有上限的,而其工作队列 SynchronousQueue 是一个没有存储空间的阻塞队列。这意味着,只要有请求到来,就必须找到一条工作线程来处理,如果当前没有空闲的线程就再创建一条新的。所以如果任务提交频率比较高,就会疯狂创建线程导致OOM

Executors.newCachedThreadPool();
//核心线程设置为0,最大线程设置为 Integer.MAX_VALUE 意味着无限大,keepAliveTime 60,工作队列SynchronousQueue 同步队列,容量为0。不设置线程工程和拒绝策略,使用默认的。
 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
     public SynchronousQueue() {
        this(false);
    }
    
    

因此,不建议使用 Executors 提供的两种快捷的线程池,原因如下:

线程池默认的工作行为:

Executors.newFixedThreadPool(1);由于工作队列无限大,所以当需要处理的任务进来没有空闲的核心线程时,直接将任务放置到工作队列中,所以核心线程数量一定等于最大线程数量;因为最大线程数量是在核心用完后,工作队列也放完之后才会增加线程到最大线程数量。

Executors.newCachedThreadPool();工作队列是没有容量的阻塞同步队列即工作队列中放不下任何任务,所以任务进来只能创建新线程执行,所以最大线程数量是近乎无限大的Integer.MAX_VALUE。核心线程数量直接设置0。60秒后线程回收。

不知道你有没有想过:Java 线程池是先用工作队列来存放来不及处理的任务满了之后再扩容线程池。当我们的工作队列设置得很大时,最大线程数这个参数显得没有意义,因为队列很难满,或者到满的时候再去扩容线程池已经于事无补了。

那么,我们有没有办法让线程池更激进一点,优先开启更多的线程,而把队列当成一个后备方案呢?

限于篇幅,这里我只给你一个大致思路:

接下来,就请你动手试试看如何实现这样一个“弹性”线程池吧。Tomcat 线程池也实现了类似的效果,可供你借鉴。

要根据任务的“轻重缓急”来指定线程池的核心参数,包括线程数、回收策略和任务队列:

上一篇 下一篇

猜你喜欢

热点阅读