线程池中你不容错过的一些细节

简介: 首先还是来复习下线程池的基本原理。我认为线程池它就是一个调度任务的工具。众所周知在初始化线程池会给定线程池的大小,假设现在我们有 1000 个线程任务需要运行,而线程池的大小为 10~20,在真正运行任务的过程中他肯定不会创建这1000个线程同时运行,而是充分利用线程池里这 10~20 个线程来调度这1000个任务。

线程池的工作原理


首先还是来复习下线程池的基本原理。


我认为线程池它就是一个调度任务的工具。


众所周知在初始化线程池会给定线程池的大小,假设现在我们有 1000 个线程任务需要运行,而线程池的大小为 10~20,在真正运行任务的过程中他肯定不会创建这1000个线程同时运行,而是充分利用线程池里这 10~20 个线程来调度这1000个任务。


而这里的 10~20 个线程最后会由线程池封装为 ThreadPoolExecutor.Worker 对象,而这个 Worker 是实现了 Runnable 接口的,所以他自己本身就是一个线程。


深入分析



这里我们来做一个模拟,创建了一个核心线程、最大线程数、阻塞队列都为2的线程池。


这里假设线程池已经完成了预热,也就是线程池内部已经创建好了两个线程 Worker


当我们往一个线程池丢一个任务会发生什么事呢?



  • 第一步是生产者,也就是任务提供者他执行了一个 execute() 方法,本质上就是往这个内部队列里放了一个任务。


  • 之前已经创建好了的 Worker 线程会执行一个 while 循环 ---> 不停的从这个内部队列里获取任务。(这一步是竞争的关系,都会抢着从队列里获取任务,由这个队列内部实现了线程安全。)


  • 获取得到一个任务后,其实也就是拿到了一个 Runnable 对象(也就是 execute(Runnable task) 这里所提交的任务),接着执行这个 Runnablerun() 方法,而不是 start(),这点需要注意后文分析原因。


结合源码来看:



从图中其实就对应了刚才提到的二三两步:


  • while 循环,从 getTask() 方法中一直不停的获取任务。


  • 拿到任务后,执行它的 run() 方法。


这样一个线程就调度完毕,然后再次进入循环从队列里取任务并不断的进行调度。


再次解释之前的问题


接下来回顾一下我们上一篇文章所提到的,导致一个线程没有运行的根本原因是:


在单个线程的线程池中一但抛出了未被捕获的异常时,线程池会回收当前的线程并创建一个新的 Worker; 它也会一直不断的从队列里获取任务来执行,但由于这是一个消费线程,根本没有生产者往里边丢任务,所以它会一直 waiting 在从队列里获取任务处,所以也就造成了线上的队列没有消费,业务线程池没有执行的问题。


结合之前的那张图来看:



这里大家问的最多的一个点是,为什么会没有是根本没有生产者往里边丢任务,图中不是明明画的有一个 product 嘛?


这里确实是有些不太清楚,再次强调一次:


图中的 product 是往内部队列里写消息的生产者,并不是往这个 Consumer 所在的线程池中写任务的生产者。


因为即便 Consumer 是一个单线程的线程池,它依然具有一个常规线程池所具备的所有条件:


  • Worker 调度线程,也就是线程池运行的线程;虽然只有一个。


  • 内部的阻塞队列;虽然长度只有1。


再次结合图来看:



所以之前提到的【没有生产者往里边丢任务】是指右图放大后的那一块,也就是内部队列并没有其他线程往里边丢任务执行 execute() 方法。


而一旦发生未捕获的异常后,Worker1 被回收,顺带的它所调度的线程 task1(这个task1 也就是在执行一个 while 循环消费左图中的那个队列) 也会被回收掉。


新创建的 Worker2 会取代 Worker1 继续执行 while 循环从内部队列里获取任务,但此时这个队列就一直会是空的,所以也就是处于 Waiting 状态。


为什是 run() 而不是 start()


问题搞清楚后来想想为什么线程池在调度的时候执行的是 Runnablerun() 方法,而不是 start() 方法呢?


我相信大部分没有看过源码的同学心中第一个印象就应该是执行的 start() 方法;

因为不管是学校老师,还是网上大牛讲的都是只有执行了start() 方法后操作系统才会给我们创建一个独立的线程来运行,而 run() 方法只是一个普通的方法调用。


而在线程池这个场景中却恰好就是要利用它只是一个普通方法调用


回到我在文初中所提到的:我认为线程池它就是一个调度任务的工具。


假设这里是调用的 Runnablestart 方法,那会发生什么事情。


如果我们往一个核心、最大线程数为 2 的线程池里丢了 1000 个任务,那么它会额外的创建 1000 个线程,同时每个任务都是异步执行的,一下子就执行完毕了


从而没法做到由这两个 Worker 线程来调度这 1000 个任务,而只有当做一个同步阻塞的 run() 方法调用时才能满足这个要求。



目录
打赏
0
0
0
0
1
分享
相关文章
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
Java并发编程:线程池的原理与实践
【5月更文挑战第30天】 在现代软件开发中,尤其是Java应用中,并发编程是一个不可忽视的领域。线程池作为提升应用性能和资源利用率的关键技术之一,其正确使用和优化对系统稳定性和效率至关重要。本文将深入探讨线程池的核心原理、常见类型以及在实际开发中的使用案例,旨在帮助开发者更好地理解和运用线程池技术,构建高性能的Java应用程序。
【面试问题】如果让你设计一个线程池如何设计?
【1月更文挑战第27天】【面试问题】如果让你设计一个线程池如何设计?
程序员的金三银四:创建线程池有哪几种方式?
程序员的金三银四:创建线程池有哪几种方式?
95 0
驾驭Java线程池:一步一步带你从新手到高手!
java框架中例如Tomcat、Dubbo等都离不开线程池,这些框架用到线程的地方,都会用线程池来负责。我们在使用这些框架的时候,会设置线程池参数,用于提高性能。那么开多少线程合适?今天我们将围绕这个问题来学习一下线程池。
200 2
驾驭Java线程池:一步一步带你从新手到高手!
一目了然!谁能想到Java多线程设计模式竟然被图解,看完不服不行
多线程设计模式在Java编程中起着至关重要的作用,它能够有效提高程序的执行效率,使得程序在处理大量数据和复杂任务时更加高效。然而,对于初学者来说,理解和应用多线程设计模式可能是一项相当具有挑战性的任务。为了让读者更加轻松地掌握这一复杂主题,我们带着一种全新的图解方式,深入剖析Java多线程设计模式的精髓。
库调多了,都忘了最基础的概念-《线程池篇》
库调多了,都忘了最基础的概念-《线程池篇》
145 0
库调多了,都忘了最基础的概念-《线程池篇》
【Java并发编程系列7】线程池基本知识
目前书籍《Java并发编程实战》看到“第7章:取消与关闭”,里面涉及到部分线程池的内容,然后第8章就是线程池,所以打算把之前看的线程池的资料再整理一下,便于后面能更好理解书中的内容。 之前看过一篇博客,关于线程池的内容讲解的非常好,我只截取基础知识部分,把Java基础内容全部掌握后,再对里面的原理部分进行深入理解,后面会附上该篇博客的链接。
175 0
【Java并发编程系列7】线程池基本知识
手撸一款简单高效的线程池(二)
大家好,我是不会写代码的纯序员——Chunel Feng[1]。这篇文章是线程池优化系列连载的第二篇。主要跟大家介绍几种线程池优化思路,包括:local-thread 机制、lock-free 机制、work-stealing 机制。
567 0
手撸一款简单高效的线程池(二)
手撸一款简单高效的线程池(一)
线程池大家应该都用过,不过如何从 0 到 1 的设计一款简单好用且性能较好的线程池?我们在接下来的几篇文章中,为您一一介绍。
544 0
手撸一款简单高效的线程池(一)
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等