笔记09线程池刨根问底

作者:神秘网友 发布时间:2021-01-12 20:21:54

笔记09线程池刨根问底

我们知道CPU运行的最小单位是线程,Java中实现并发是通过多线程来完成的,利用多线程提高了对CPU资源的利用率,但是线程的创建和销毁是很消耗性能的。线程的创建伴随着虚拟机栈、本地方法栈、程序计数器等线程私有内存空间的创建,在线程销毁的时候也伴随着这些私有内存的回收,频繁的创建和销毁线程会占用大量的系统资源,而对线程的复用则可以有效的管理和协调线程的工作。

线程池主要解决的两个问题:

  • 1. 在执行大量异步任务的时候线程池能够提供很好的性能;
  • 2. 线程池能够提供一种资源限制和管理的手段,比如限制同时运行的线程的数量。

线程池的体系

笔记09线程池刨根问底

Executor:线程池最顶端的接口,在Executor中只有一个execute方法,用于执行任务。线程的创建、调度都是由子类实现的;

ExecutorService:继承自Executor,在Executor内部实现了任务提交机制以及线程池关闭的方法;

ThreadPoolExecutor:ExecutorService的默认实现,线程池的机制大部分封装在这个类中;

ScheduledExecutorService:继承自ExecutorService,增加了定时任务;

ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor,并实现了ScheduledExecutorService接口,提供定时任务处理;

ForkJoinPool:一种任务分解的线程池,需要配合ForkJoinTask来使用。

线程池的创建

JDK中为开发者提供了一个线程池的工厂类 - Executors,Executors中提供了多个静态方法,用来创建不同配置的线程池。

  • newSingleThreadExecutor

创建一个单线程化的线程池,它里面只有一个工作线程,所有提交的异步任务都会放到一个先进先出的队列中按顺序执行。

笔记09线程池刨根问底

所有的异步任务都使用同一个线程执行,且异步任务执行的顺序就是异步任务被提交到线程池中的顺序:

笔记09线程池刨根问底
  • newCachedThreadPool

创建一个可缓存线程的线程池,当线程池中线程的长度超过了需要的数量,则会回收多余的线程,若没有可用线程,就会创建新的线程。

笔记09线程池刨根问底

上面的代码中一次性提交了5个异步任务,每个异步任务内部耗时操作500ms,此时线程池就会不断创建新的线程来执行异步任务:

笔记09线程池刨根问底

下面修改一下代码,每次向线程池中提交异步任务的时候等待1000ms:

笔记09线程池刨根问底

再次执行代码,可以看到所有的异步任务会按顺序的在同一个线程中被执行,这是因为单个异步任务执行完毕之后,线程池并不会立即销毁线程,而是将线程缓存起来,等待下一个异步任务的提交,继续使用已经创建的线程来执行后面的异步任务,这样极大的减少了系统因为线程创建和销毁的损耗。

  • newFixedThreadPool

创建一个线程数量固定、可重用的线程池。在使用newFixedThreadPool创建线程池的时候需要指定线程池中常驻线程的数量,在后续异步任务提交的时候,不管提交了多少个Runnable任务,线程池中始终只有指定数量的线程处理任务。

笔记09线程池刨根问底

上面的代码中向newFixedThreadPool中一次性提交了10个任务,但是任务只会被3个线程分配执行。

笔记09线程池刨根问底

newScheduledThreadPool

会创建一个可以处理定时任务的线程池,支持定时任务和周期性任务的执行。

笔记09线程池刨根问底

上面的代码中创建了一个线程数量为2的定时线程池,通过scheduleAtFixedRate指定每隔500毫秒执行一次任务,并在5秒之后通过shutdown关闭定时任务。

线程池工作原理

线程池的机构:

笔记09线程池刨根问底

上图是一个线程池的结构,在一个线程池中包含以下几部分:

  • worker集合:使用HashSet保存所有核心线程和非核心线程;
  • 等待队列:当核心核心线程的数量达到corePoolSize的时候,新提交的任务会被保存到等待队列中,等待队列是一个阻塞队列BlockingQueue;
  • ctl:是一个AtomicInterger类型的数据,二进制高3位保存线程池的状态,低29位保存线程池中线程的数量;

线程池的构造方法

笔记09线程池刨根问底

构造函数的参数说明:

  • corePoolSize:表示核心线程的数量;
  • maximumPoolSize:表示线程池最大可容纳同时执行的线程数量,必须大于等于1。如果maximumPoolSize等于corePoolSize,则为固定大小的线程池;
  • keepAliveTime:表示线程池中线程空闲的时长,当线程空闲的时长达到这个值的时候,线程就会被销毁,直到线程池中剩余corePoolSize个线程;
  • unit:用来指定keepAliveTime的时间单位;
  • workQueue:等待队列,BlockingQueue类型,当请求数超过corePoolSize,任务会被首先缓存到等待队列中;
  • threadFactory:线程工厂,线程池中使用它来创建线程;
  • handler:执行拒绝策略的对象,当workQueue中的任务塞满,并且活动线程数量超过了maximumPoolSize的时候,线程池会通过该策略处理新的任务;

流程解析:

当我们通过execute或者submit向线程池提交任务,线程池在接收到任务之后,有以下几种情况:

  • 1. 当前线程池中运行的线程数量还没有达到corePoolSize数量,线程池会创建一个新的线程来执行当前提交的任务,无论之前创建的线程是否处于空闲状态;
  • 2. 当前线程池中运行的线程数量已经达到了corePoolSize数量,线程池会将新加入的任务放到等待队列中,直到某一个线程空闲了,线程池会根据等待队列中设置的优先级规则,取出一个任务执行;
  • 3. 要是线程池中运行的线程数量大于corePoolSize,并且等待队列已满,但运行线程数量并没有达到最大线程数maximumPoolSize,此时线程池会创建新的线程来执行任务;
  • 4. 要是提交的任务无法被核心线程处理,等待队列也已经塞满,有无法被非核心线程处理,线程池就会按照拒绝处理器(handler)来处理这个任务。如果没有为线程池定义RejectExecutionHandler,那么线程池就会抛出一个RejectExecutionException异常。

JDK中定义了4种保护策略:

笔记09线程池刨根问底

禁止使用Executors

我们在使用多线程的时候,一般禁止使用线程池的工厂类进行线程池的创建,尤其是newFixedThreadPool和newCachedThreadPool这两个方法。

我们查看newFixedThreadPool方法和newSingleThreadExecutor方法创建线程池的过程:

笔记09线程池刨根问底

可以看到线程池中传入了一个无界的阻塞队列,理论上可以添加无限个任务到线程池中,当我们向等待队列中塞入特别多的任务的时候,由于等待队列无限长,线程池永远不会执行任务丢弃的保护逻辑,就会导致OOM发生。

我们在看一下newCachedThreadPool的实现代码:

笔记09线程池刨根问底

newCachedThreadPool在创建线程池的时候,声明的maximumPoolSize为Interger的最大值,当核心线程耗时很久的时候,线程池就会尝试创建新的线程执行任务,当内存中无法承受新线程的创建时,就会导致OOM发生。

?

笔记09线程池刨根问底 相关文章

  1. 线程的消息队列是怎么创建的

    可以在子线程中创建handler么? 主线程的Looper和子线程的Looper有什么区别? Looper和MessageQueue有什么关系? MessageQueue是怎么创建的? 上图中我们在子线程中创建一个Handler对象,会抛出异常。异常信息说明不能在没有调用Looper.prepare()的线程中创

  2. 笔记10DVM或ART是如何对JVM进行优化的

    DVM大多数实现和传统的JVM相似,但是为了满足Android在手机端内存的限制,Dalvik对JVM做了一些独有的优化。 Dex文件 传统的class文件是由Java源码文件编译生成的,而Android在编译打包的时候,会将所有的class文件整合优化,最终生成class.dex文件。dex文件

  3. Android线程间消息传递机制

    上图是消息循环的过程,当线程进入Looper.loop()循环之后,会从MessageQueue中阻塞的读取Message,要是MessageQueue中没有消息,会一直阻塞在queue.next的地方,直到从MessageQueue中读取到Message,然后将该Message分发给Message的target,这个target是一个

  4. R语言进阶笔记5purrr替代循环

    purrr 替代循环 1 purrr循环 引用知乎张敬信的说法: ?用 R 写 「循环」 从低到高有三种境界:手动 for 循环,apply 函数族,purrr 包泛函式编程。? R写循环有三个境界: 手动for循环 apply循环 purrr泛函式编程 其中,手动for循环我最常用,apply系列半吊子

  5. R语言进阶笔记4dplyr 汇总统计

    之前写过一篇博文(汇总统计?一个函数全部搞定!),介绍R中编写一个函数,进行汇总统计。效果很不错。今天用 tidyverse 包实现一下,多角度尝试,然后尝试中学习。 1. 想要达到的效果 最近,一个朋友让我帮忙做一个图标,是这个样子的: 相关的统计参数:

  6. 【计算机网络】学习笔记第六篇:应用层(谢希仁版)

    说明: 文章主要针对科班上课做的简单笔记,以及后面针对一些面试涉及内容的详细讲解 再三声明:考研的同学好好去背书,做题,重点研究一下侧重点,加油 !!! 此文章归纳整理自:【计算机网络】(第七版)谢希仁 ,一切内容版权均归书籍作者所有,侵删 一

  7. 【计算机网络】学习笔记第五篇:运输层(谢希仁版)

    说明: 文章主要针对科班上课做的简单笔记,以及后面针对一些面试涉及内容的详细讲解 再三声明:考研的同学好好去背书,做题,重点研究一下侧重点,加油 !!! 此文章归纳整理自:【计算机网络】(第七版)谢希仁 ,一切内容版权均归书籍作者所有,侵删 一

  8. 云原生技术之docker学习笔记(2)

    // 云原生技术之docker学习笔记(2) // 今天我们看看Docker镜像相关内容。 Docker镜像说明 1、什么是Docker镜像? docker镜像是由多个文件系统叠加而成的,最底层是一个引导文件系统,也就是bootfs,用户基本上不会和底层的引导文件系统有任何交互。所以这块

  9. 云原生技术之docker学习笔记(1)

    // 云原生技术之docker学习笔记(1) // 最近的工作中,MySQL和MongoDB运维部分的内容比较少。主要工作内容都会频繁接触docker和k8s相关的内容,所以准备写写这块儿的内容。 之前的文章中,介绍过docker上部署MySQL的过程,这里贴出来链接,方便回顾: docker

  10. 云原生技术之docker学习笔记(3)

    // 云原生技术之docker学习笔记(3) // 今天我们看看Dockerfile创建的方法。 构建docker镜像,有两种方法: 1、一种是使用docker commit命令 2、另外一种是基于docker build命令和dockerfile文件 通常情况下,dockerfile构建镜像比docker commit命令构建 镜像

每天更新java,php,javaScript,go,python,nodejs,vue,android,mysql等相关技术教程,教程由网友分享而来,欢迎大家分享IT技术教程到本站,帮助自己同时也帮助他人!

Copyright 2020, All Rights Reserved. Powered by 跳墙网(www.tqwba.com)|网站地图|关键词