某开发手册为什么推荐手动创建线程池

126 阅读3分钟

在程序员的日常工作中,免不了使用多线程,那么就离不开线程池,大家都是怎么使用线程池的呢?

小菜(本人)刚开始是使用java中自带的java.util.concurrent.Executors类,通过Executors.newFixedThreadPool(5)

方法创建线程池,并无不妥;直到使用了某开发手册后,竟然提醒

**FixedThreadPool中允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM**

,于是决定一探究竟.

线程池的核心参数

通过java.util.concurrent.ThreadPoolExecutor类,找到其构造函数,如下:

/**     * Creates a new {@code ThreadPoolExecutor} with the given initial     * parameters.     *     * @param corePoolSize the number of threads to keep in the pool, even     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set     * @param maximumPoolSize the maximum number of threads to allow in the     *        pool     * @param keepAliveTime when the number of threads is greater than     *        the core, this is the maximum time that excess idle threads     *        will wait for new tasks before terminating.     * @param unit the time unit for the {@code keepAliveTime} argument     * @param workQueue the queue to use for holding tasks before they are     *        executed.  This queue will hold only the {@code Runnable}     *        tasks submitted by the {@code execute} method.     * @param threadFactory the factory to use when the executor     *        creates a new thread     * @param handler the handler to use when execution is blocked     *        because the thread bounds and queue capacities are reached     * @throws IllegalArgumentException if one of the following holds:<br>     *         {@code corePoolSize < 0}<br>     *         {@code keepAliveTime < 0}<br>     *         {@code maximumPoolSize <= 0}<br>     *         {@code maximumPoolSize < corePoolSize}     * @throws NullPointerException if {@code workQueue}     *         or {@code threadFactory} or {@code handler} is null     */    public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) {        if (corePoolSize < 0 ||            maximumPoolSize <= 0 ||            maximumPoolSize < corePoolSize ||            keepAliveTime < 0)            throw new IllegalArgumentException();        if (workQueue == null || threadFactory == null || handler == null)            throw new NullPointerException();        this.acc = System.getSecurityManager() == null ?                null :                AccessController.getContext();        this.corePoolSize = corePoolSize;        this.maximumPoolSize = maximumPoolSize;        this.workQueue = workQueue;        this.keepAliveTime = unit.toNanos(keepAliveTime);        this.threadFactory = threadFactory;        this.handler = handler;    }

发现有7个必传参数,分别是:

corePoolSize: 核心线程数,开始就创建好的,可以随时执行任务,要求是必须大于0
maximumPoolSize: 最大线程数,要求是大于等于核心线程数
keepAliveTime: 非核心线程的空闲存活时间,要求是必须大于0
unit: 时间单位,与keepAliveTime配合使用
workQueue :当线程池满了,任务就会放入队列中等待被执行
threadFactory: 线程工厂,可以决定线程的名字,属性等
handler: 拒绝处理程序策略,workQueue 被填满,继续接收任务时,会抛出异常

为什么可能会oom

有了以上知识基础,就可以进行下一步了,进入Executors.newFixedThreadPool(5)方法内部

/**     * Creates a thread pool that reuses a fixed number of threads     * operating off a shared unbounded queue.  At any point, at most     * {@code nThreads} threads will be active processing tasks.     * If additional tasks are submitted when all threads are active,     * they will wait in the queue until a thread is available.     * If any thread terminates due to a failure during execution     * prior to shutdown, a new one will take its place if needed to     * execute subsequent tasks.  The threads in the pool will exist     * until it is explicitly {@link ExecutorService#shutdown shutdown}.     *     * @param nThreads the number of threads in the pool     * @return the newly created thread pool     * @throws IllegalArgumentException if {@code nThreads <= 0}     */    public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }

可以看到LinkedBlockingQueue队列没有指定大小,那么就会使用默认值,默认值是多少呢?我们点进去看一下

/**     * Creates a {@code LinkedBlockingQueue} with a capacity of     * {@link Integer#MAX_VALUE}.     */    public LinkedBlockingQueue() {        this(Integer.MAX_VALUE);    }

Integer的最大值,也就是说线程池中可以不停的接收任务,可能内存已经放不下,队列还没满,这时就会出现oom了.

以下是一个正例:

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 3,            TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(5)    );

引出下一个问题

当maximumPoolSized大于corePoolSize ,什么时候会创建新线程

上面的例子中:核心线程数是5,最大线程数是10,空闲非核心线程的存活时间为3分钟,可等待执行的任务数是5,线

程工厂和拒绝策略暂不考虑,使用默认的即可.很多人想当然的认为,当线程池中的任务大于5时,就会创建新的线程执

行任务,直到达到10时,任务才会被放入队列等待.其实不是的,真实情况如下图所示:

  • 当线程池中的缓存队列满时,才会创建新的线程执行任务,否则会一直等待核心线程的释放.

  • 当非核心线程达到最大值且队列满时,有新的任务提交,就会被拒绝,并抛出RejectedExecutionException.

有兴趣的同学可以思考下,为什么不是先创建新线程执行,后放入队列等待.

在文章的结尾,总结下核心的两个知识点:

  1. 尽量手动创建线程池,了解必要的参数,避免安全隐患.

  2. 核心线程用尽时,线程池的执行策略是先放入队列等待,然后根据情况判断是否创建新线程.