博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
90%的Java工程师都不了解的线程池细节问题!
阅读量:3923 次
发布时间:2019-05-23

本文共 2006 字,大约阅读时间需要 6 分钟。

公众号后台回复 “资料” 

获取作者独家秘制学习资料

线程池的工作原理

首先复习下线程池的基本原理,我认为线程池它就是一个调度任务的工具。

众所周知,在初始化线程池会给定线程池的大小,假设现在我们有 1000 个线程任务需要运行,而线程池的大小为 10~20。

在真正运行任务的过程中他肯定不会创建这1000个线程同时运行,而是充分利用线程池里这 10~20 个线程来调度这1000个任务。

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

深入分析

640?wx_fmt=jpeg

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

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

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

640?wx_fmt=jpeg

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

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

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

结合源码来看:

640?wx_fmt=jpeg

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

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

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

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

再次解释之前的问题

接下来回顾一下以前提到的问题:导致一个线程没有运行的根本原因是?

在单个线程的线程池中一旦抛出了未被捕获的异常时,线程池会回收当前的线程,并创建一个新的 Worker

它也会一直不断的从队列里获取任务来执行,但由于这是一个消费线程,根本没有生产者往里边丢任务,因此它会一直 waiting 在从队列里获取任务处。

所以也就造成了线上的队列没有消费,业务线程池没有执行的问题。

结合之前的那张图来看:

640?wx_fmt=jpeg

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

这里确实是有些不太清楚,再次强调一次:图中的 product 是往内部队列里写消息的生产者,并不是往这个 Consumer 所在的线程池中写任务的生产者。

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

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

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

再次结合图来看:

640?wx_fmt=jpeg

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

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

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

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

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

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

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

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

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

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

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

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

END

欢迎长按下图关注公众号石杉的架构笔记,后台回复“资料”,获取作者独家秘制学习资料!

640?wx_fmt=jpeg

BAT架构经验倾囊相授

640?wx_fmt=gif

转载地址:http://purrn.baihongyu.com/

你可能感兴趣的文章
结构型设计模式总结
查看>>
dotNET:怎样处理程序中的异常(实战篇)?
查看>>
What is 测试金字塔?
查看>>
.Net Core HttpClient处理响应压缩
查看>>
十分钟搭建自己的私有NuGet服务器-BaGet
查看>>
efcore 新特性 SaveChanges Events
查看>>
龙芯3A5000初样顺利交付流片
查看>>
用了Dapper之后通篇还是SqlConnection,真的看不下去了
查看>>
ABP快速开发一个.NET Core电商平台
查看>>
[NewLife.Net]单机400万长连接压力测试
查看>>
使用Azure人脸API对图片进行人脸识别
查看>>
快醒醒,C# 9 中又来了一堆关键词 init,record,with
查看>>
【招聘(深圳)】轻岁 诚聘.NET Core开发
查看>>
欢迎来到 C# 9.0(Welcome to C# 9.0)
查看>>
Dapr微服务应用开发系列1:环境配置
查看>>
使用 Visual Studio 2019 批量添加代码文件头
查看>>
【BCVP更新】StackExchange.Redis 的异步开发方式
查看>>
.NET5.0 Preview 8 开箱教程
查看>>
真・WPF 按钮拖动和调整大小
查看>>
做权限认证,还不了解IdentityServer4?不二话,赶紧拥抱吧,.NET Core官方推荐!...
查看>>