专栏首页用户5521492的专栏女同事问狗哥什么是线程池的阻塞队列?

女同事问狗哥什么是线程池的阻塞队列?

线程池

欢迎来到狗哥多线程系列连载。本篇是线程相关的第八篇,前七篇分别是:

创建线程到底有几种方式?

线程有多少种状态?Runnable 一定在执行任务吗?

万字长文,Thread 类源码解析!

wait、notify/notifyAll 解析

线程之生产者消费者模式

狗哥肝了一下午的线程池

线程池的拒绝策略

线程池的内部结构

来源:拉勾教育 Java 并发编程.png

如图所示,线程池的内部结构主要由线程池管理器、工作线程、任务队列以及任务四部分组成。

线程池的阻塞队列

先上张图,表格左侧是线程池,右侧为它们对应的阻塞队列,你可以看到 5 种线程池对应了 3 种阻塞队列。

图源:拉勾教育 Java 并发编程~阻塞队列.png

下面逐一说下它们的特点:

  • LinkedBlockingQueue,底层是链表结构、采用先进先出原则,默认容量是 Integer.MAX_VALUE,几乎可以认为是无界队列(几乎不可能达到这个数)。而由于 FixedThreadPool 和 SingleThreadExecutor 的线程数是固定的,所以只能用容量无穷大的队列来存任务
  • SynchronousQueue,容量为 0,不做存储,只做转发。由于 CachedThreadPool 的最大线程数是 Integer.MAX_VALUE,有任务提交就转发给线程或者创建新线程来执行,并不需要队列存储任务。所以在自定义使用 SynchronousQueue 的线程池应该把最大线程数设置得尽量大,避免任务数大于最大线程数时,没办法把任务放到队列中也没有足够线程来执行任务的情况。
  • DelayedWorkQueue,内部用的是堆数据结构,初始容量为 16,跟 hashmap 一样动态扩容,对任务延时长短进行排序

为什么不自动创建线程池?

阿里巴巴 Java 规约也约定了,手动创建线程池,效果会更好。为什么呢?回答这个问题之前,先来看看五种线程池初始化的方法:

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

// SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0 L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue <
        Runnable > ()));
}

首先是 FixedThreadPool 和 SingleThreadExecutor,它两的问题在于都是用默认容量的无界队列 LinkedBlockingQueue,当任务处理慢时,队列迅速积压任务并占用大量内存,发生 OOM(内存溢出)。所以在使用时我们可以根据业务指定队列长度:

ExecutorService threadPool = new ThreadPoolExecutor(2, 5,
    1 L, TimeUnit.SECONDS,
    new LinkedBlockingQueue < > (3),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy());

然后是 CachedThreadPool,也可以发现一个问题:它默认的最大线程是 Integer.MAX_VALUE,当任务贼多时,它就会不断创建线程,而线程执行比较耗时来不及回收。最终也会造成 OOM,所以应该手动指定最大线程数。

// CachedThreadPool
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60 L, TimeUnit.SECONDS, new SynchronousQueue < Runnable > ());
}

最后是 ScheduledThreadPool 和 ScheduledThreadPoolExecutor,这两问题就更大了。首先最大线程数是 Integer.MAX_VALUE,然后阻塞队列是 DelayedWorkQueue,它也是无界队列,最终还是会造成 OOM。

// ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

// ScheduledThreadPoolExecutor
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}

-END-

如果看到这里,喜欢这篇文章的话,请帮点个好看。微信搜索「一个优秀的废人」,关注后回复「 1024」送你一套完整的 java 教程(包括视频)。回复「 电子书」送你全编程领域电子书 (不只Java)。

教程节选

本文分享自微信公众号 - 一个优秀的废人(feiren_java),作者:nasus

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-10-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 40 个Java多线程问题总结

    原文地址:http://www.cnblogs.com/xrq730/p/5060921.htm

    一个优秀的废人
  • 谈谈notify和notifyAll的异同

    经常在网上逛,关于在java中notify和notifyAll,经常有人有以下的说法:

    一个优秀的废人
  • Java 并发编程 71 道面试题及答案

    任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。Thre...

    一个优秀的废人
  • Java 线程池框架核心代码分析

    多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和资源消耗都是很高的。线程池应运而生,成为我们管理线程的利器。Java 通过Executor接口,...

    哲洛不闹
  • Java线程池使用说明

    现在是资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享。

    互扯程序
  • 五种线程池的对比与使用

    通过源码可以看出底层调用的是ThreadPoolExecutor方法,传入一个同步的阻塞队列实现缓存。

    爱撸猫的杰
  • java 线程池简介

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    DencyCheng
  • Linux并发(多线程协作)

    一个程序里的线程数,就像一家公司里的员工数一样,太少了忙不过来,太多了入不敷出。因此我们需要有更好的机制来协调它们。

    用户2617681
  • 讲真 这次绝对让你轻松学习线程池

    老王 是个深耕在帝都的一线码农,辛苦一年挣了点钱,想把钱存储到银行卡里,钱银行卡办理遇到了如下的遭遇

    sowhat1412
  • Java 线程 Executor 框架详解与使用

    在HotSpot VM的线程模型中,Java线程被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,这个操作系...

    哲洛不闹

扫码关注云+社区

领取腾讯云代金券