Java 核心技术面试精讲
杨晓峰
前 Oracle 首席工程师
125942 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 44 讲
Java 核心技术面试精讲
15
15
1.0x
00:00/00:00
登录|注册

第21讲 | Java并发类库提供的线程池有哪几种? 分别有什么特点?

并行地处理任务,不保证处理顺序
Java 8新增
创建多个工作线程的ScheduledExecutorService
创建单一工作线程的ScheduledExecutorService
无界的工作队列
工作线程数目被限制为1
使用无界的工作队列
重用指定数目的线程
使用SynchronousQueue作为工作队列
试图缓存线程并重用
用于处理大量短时间工作任务
高度的可调节性和灵活性
提供返回结果的submit方法
提供更全面的提交任务机制
提供service的管理功能
提供execute方法
初衷是将任务提交和任务执行细节解耦
newWorkStealingPool(int parallelism)
newScheduledThreadPool(int corePoolSize)
newSingleThreadScheduledExecutor()
newSingleThreadExecutor()
newFixedThreadPool(int nThreads)
newCachedThreadPool()
考虑系统资源限制
I/O操作任务
CPU计算任务
尽量避免在使用线程池时操作ThreadLocal
避免死锁等同步问题
避免过度扩展线程
避免任务堆积
TERMINATED
TIDYING
STOP
SHUTDOWN
RUNNING
RejectedExecutionHandler
ThreadFactory
workQueue
TimeUnit
keepAliveTime
maximumPoolSize
corePoolSize
线程池的设计特点
ExecutorService
Executor
Executors
线程池大小的选择策略
线程池实践
线程池的状态
线程池的构造函数
Executor框架
Java并发类库提供的线程池有哪几种? 分别有什么特点?

该思维导图由 AI 生成,仅供参考

我在专栏第 17 讲中介绍过线程是不能够重复启动的,创建或销毁线程存在一定的开销,所以利用线程池技术来提高系统资源利用效率,并简化线程管理,已经是非常成熟的选择。
今天我要问你的问题是,Java 并发类库提供的线程池有哪几种? 分别有什么特点?

典型回答

通常开发者都是利用 Executors 提供的通用线程池创建方法,去创建不同配置的线程池,主要区别在于不同的 ExecutorService 类型或者不同的初始参数。
Executors 目前提供了 5 种不同的线程池创建配置:
newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列。
newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads。
newSingleThreadExecutor(),它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。
newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize),创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Java并发编程中的线程池是一种重要的工具,提供了多种类型的线程池,适用于不同类型的任务处理。这些线程池具有高度的可调节性和灵活性,可以根据实际需求配置不同的参数,创建出行为大相径庭的线程池。然而,在使用线程池时需要注意避免任务堆积、过度扩展线程、线程泄漏和死锁等同步问题,以及合理选择线程池大小。虽然大多数情况下使用Executors提供的静态工厂方法已经足够,但深入学习Executor框架的主要内容、线程池和相关并发工具类型的理解,以及实践中常见问题的诊断思路,都是值得深入学习的内容。文章还提到了在解决大负载问题时,除了调整线程池外,架构上的改变也能更好地解决问题。因此,合理使用线程池需要根据自身应用特点进行选择。整体而言,本文介绍了Java中不同类型的线程池及其使用注意事项,对于想要深入了解并发编程的读者具有一定的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 核心技术面试精讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(37)

  • 最新
  • 精选
  • I am a psycho
    通过看源码可以得知,core和max都是1,而且通过FinalizableDelegatedExecutorService进行了包装,保证线程池无法修改。同时shutdown方法通过调用interruptIdleWorkers方法,去停掉没有工作的线程,而shutdownNow方法是直接粗暴的停掉所有线程。无论是shutdown还是shutdownNow都不会进行等待,都会直接将线程池状态设置成shutdown或者stop,如果需要等待,需要调用awaitTernination方法。查找了一下threadFactory的使用,只找到了在worker创建的时候,用来初始化了线程。

    作者回复: 不错,很棒的总结; 我问threadFactory次数,其实是问worker都在什么情况下会被创建,比如,比较特别的,任务抛异常时;随便自定义一个threadfactory,模拟提交任务就能体会到

    2018-06-23
    3
    64
  • 李二木
    我觉得还有一点很重要,就是放在线程池中的线程要捕获异常,如果直接抛出异常,每次都会创建线程,也就等于线程池没有发挥作用,如果大并发下一直创建线程可能会导致JVM挂掉。最近遇到的一个坑

    作者回复: 任务出异常是要避免

    2018-06-23
    5
    51
  • 约书亚
    疑问,为什么当初sun的线程池模式要设计成队列满了才能创建非核心线程?类比其他类似池的功能实现,很多都是设置最小数最大数,达到最大数才向等待队列里加入,比如有的连接池实现。

    作者回复: Doug Lea这个实现基本是工业标准了,除非特定场景需求

    2018-06-23
    10
    27
  • 沈琦斌
    老师,我想问的是cache的线程池大小是1,每次还要新创建,那和我自己创建而不用线程池有什么区别?

    作者回复: 你是说cachedthreadpool?那个大小是浮动的,不是1;如果说single,executorservice毕竟还提供了工作队列,生命周期管理,工作线程维护等很多事,还是要高效

    2018-06-28
    15
  • 饭粒
    写了个简单demo玩了下。 创建线程池会初始化线程工厂,工作线程是在提交任务的创建的。工作线程在执行任务中抛出异常,再次提交任务会又新建工作线程。newFixedThreadPool 正常执行任务时会优先创建线程已达到核心线程数,不会优先复用空闲工作线程。 ``` /** * 线程池工作线程执行任务抛出异常 */ @Test public void test03() throws InterruptedException { // java.util.concurrent.Executors.DefaultThreadFactory.DefaultThreadFactory 构造线程工厂 ExecutorService executorService = Executors.newCachedThreadPool(); Runnable task = new Runnable() { @Override public void run() { System.out.println("hello world"); // 抛出异常 throw new RuntimeException(); } }; executorService.execute(task); // 提交任务通过 DefaultThreadFactory.newThread() 创建线程 TimeUnit.SECONDS.sleep(2); // 前一个工作线程在执行任务中抛出异常,再提交任务又会新建工作线程 executorService.execute(task); TimeUnit.SECONDS.sleep(3); } ```

    作者回复: 实践是好习惯

    2019-01-15
    2
    11
  • 王磊
    core和max应该都是1。验证的方法是自己写一个Threadlocal, 里面有相应创建线程的日志,然后把它传入创建线程池。

    作者回复: core和Max源码或者逻辑分析都很清楚;而创建线程次数理论上是不确定的,比如任务执行中抛异常,就要重新创建worker

    2018-06-25
    6
  • GK java
    线程池到底需不需要关闭

    作者回复: 通常建议明确关闭,要看具体场景,我们的应用对于关闭本身是如何定义,有没有要求,什么时机触发,需要保证优雅的退出吗?

    2019-01-26
    5
  • 杨老师,我照着文章翻看源码,下面那块是不是不太对? ---------------- Executors 目前提供了 5 种不同的线程池创建配置: newSingleThreadExecutor,它创建的是个 FinalizableDelegatedExecutorService newSingleThreadScheduledExecutor 创建的是 ScheduledThreadPoolExecutor

    作者回复: 谢谢指出

    2018-07-04
    2
    5
  • 镰仓
    听了一段时间课程,质量很高。我的需求是android JavaVM

    作者回复: android我并没有特别的经验,尽管很多方面是通用的

    2018-06-28
    3
  • 灰飞灰猪不会灰飞.烟灭
    老师 放入队列中的线程是直接调用start方法还是把队列中的线程放入线程工厂,让线程工厂执行? 另外,怎么判断一个线程是否执行完成呢?(只有执行完成才返回结果)谢谢老师

    作者回复: 工厂是创建线程;执行完成通常是说任务,而不是线程,任务才是我们关心的;可以用Future

    2018-06-25
    2
收起评论
显示
设置
留言
37
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部