热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java线程池介绍

一、线程池的概念线程池(英语:threadpool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行

一、线程池的概念

线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。


二、线程池的状态

线程池共有5种状态,分别是RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED:

RUNNING:线程池处于该状态时,可以接收新的任务、运行等待队列中的任务。

状态触发:线程池被创建。

SHUTDOWN:线程池处于该状态时,不会接收新的任务,但会执行等待队列中的任务。

状态触发:RUNNING状态下,调用shutdown()方法。

STOP:线程池处于该状态时,不会接收新的任务,不会执行等待队列中的任务,并且会中断正在处理中的任务。

状态触发:RUNNING/SHUTDOWN状态下,调用shutdownNow()方法。

TIDYING:线程池所有的任务已终止,ctl记录的任务数为0时,则会处于该状态。

状态触发:

(1)当线程池在SHUTDOWN状态下,阻塞队列为空且线程池中执行的任务也为空;

(2)当线程池在STOP状态下,线程池中执行的任务为空。

注:当线程池处于TIDYING状态时,会执行terminated()方法终止线程池。

TERMINATED:线程池彻底终止,则会处于该状态。

状态触发:TIDYING状态下,terminated()方法执行完毕。

5种状态之间的转换如下图所示:

 

 

 


三、线程池相关类

 

 

接口:

Executor:仅定义了execute(Runnable command)执行任务的方法;

ExecutorService:继承自Executor接口,新增了任务的管理方法;

ScheduledExecutorService:继承自ExecutorService接口,新增了定时处理任务的方法。

类:

AbstractExecutorService:实现了ExecutorService接口,提供了各方法的默认实现;

ThreadPoolExecutor:继承自AbstractExecutorService抽象类,线程池的实现类;

ScheduledThreadPoolExecutor:实现了ScheduledExecutorService接口,继承自ThreadPoolExecutor类,因此可以将其看成是基本线程池类的扩展版,提供了线程池任务的延迟/定时处理功能;

DelegatedExecutorService:Executors工具类中用到,不关心;

FinalizableDelegatedExecutorService:Executors工具类中用到,不关心;

DelegatedScheduledExecutorService:Executors工具类中用到,不关心;

ForkJoinPool:fork/join框架中用到,不关心;

ThreadPerTaskExecutor:fork/join框架中用到,不关心。

 

线程池的重点实现类有两个:ThreadPoolExecutor和ScheduledThreadPoolExecutor。

 


ThreadPoolExecutor

ThreadPoolExecutor的构造函数有以下4个:

 

 

其中前3个构造函数,都是调用的最后一个构造函数,该函数存在7个参数,各参数的说明如下:


corePoolSize

类型:int

说明:线程池核心执行线程数,该值不能小于0。所谓核心线程,指的是线程池中的常驻存活线程数量,无论是否有任务需要执行,即使线程为空闲状态,也不会关闭。


maximumPoolSize

类型:int

说明:线程池最大执行线程数,该值必须大于0。当线程池中的核心线程全部在工作,并且等待队列已满,则线程池会创建新的线程处理任务,该参数限定的就是核心线程数+额外创建的线程数总和,换句话说,无论如何,线程池里可执行的线程数量不可能超出该参数设置的数量。


keepAliveTime

类型:long

说明:空闲线程等待工作的最大存活时间,该值不能小于0。空闲线程指的是除了核心线程之外,线程池为了处理任务而额外创建的其他线程,当任务处理完毕,额外创建的线程处于空闲状态,即为空闲线程。此时若设置了该参数,则空闲线程会在指定时间内存活,超出时间后线程会被销毁。当该参数设置为0时,代表空闲线程立刻销毁。

注:默认情况下,核心线程是不会受到该参数的影响的,但是可以通过调用allowCoreThreadTimeOut(boolean value)方法,传入的布尔值用于设置是否允许核心线程也通过此参数进行线程的超时销毁。若传入value值为true的话,则keepAliveTime参数必须要大于0。


unit

类型:TimeUnit

说明:空闲线程存活的时间单位,与keepAliveTime配合使用,不能为空。类型TimeUnit为枚举类,一般直接根据情况使用枚举值即可,如TimeUnit.SECONDS、TimeUnit.MINUTES等。


workQueue

类型:BlockingQueue

说明:任务队列,不能为空。该队列用于保存等待执行的任务。从队列元素数量的有限与无限来区分,任务队列可分为有界队列和无界队列,前者可保存的元素是有限的,后者可以看成没有元素数量的限制(实际不能超过Integer.MAX_VALUE)。

实际开发中更推荐使用有界队列,因为使用无界队列,在任务添加的频率比任务处理完成的频率要高的时候,队列保存的任务数量会一直累加,直到引发OOM异常。

常用的队列有以下几种:


ArrayBlockingQueue

该队列为有界队列,使用数组保存元素。该队列存在3个构造函数:

 

 

public ArrayBlockingQueue(int capacity):可传入容量参数,默认构建非公平(FIFO)的队列;

public ArrayBlockingQueue(int capacity, boolean fair):可传入容量和锁公平参数;

public ArrayBlockingQueue(int capacity, boolean fair, Collection c):可传入容量、锁公平和初始化的元素集合,该集合会被遍历,元素依次添加到队列中。


LinkedBlockingQueue

该队列既可以是有界队列,也可以是无界队列,通过调用不同的构造函数来区分。使用链表结构保存元素。该队列存在3个构造函数:

 

 

public LinkedBlockingQueue():无参,构建队列,初始化容量为Integer.MAX_VALUE;

public LinkedBlockingQueue(int capacity):传入容量参数,构建指定容量的队列;

public LinkedBlockingQueue(Collection c):传入元素集合,构建容量为Integer.MAX_VALUE的队列,并将集合的元素依次遍历添加到队列中。


SynchronousQueue

SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。因此在使用该队列时,通常要求maximumPoolSizes设置为无界(Integer.MAX_VALUE)。该队列存在2个构造函数:

 

 

public SynchronousQueue():创建默认非公平的队列;

public SynchronousQueue(boolean fair):传入fair参数,队列指定公平性。


threadFactory

类型:ThreadFactory

说明:线程工厂,线程池中的线程均通过该工厂创建。ThreadFactory类是一个函数式接口,仅有一个方法:newThread(Runnable r),返回Thread类型的线程。

java提供了一个默认的实现类DefaultThreadFactory。


handler

类型:RejectedExecutionHandler

说明:拒绝策略,在使用线程池并且使用有界任务队列的时候,如果线程池中的线程到达最大数量,并且等待队列已满,则新任务添加到线程池的时候就会触发该策略进行处理。RejectedExecutionHandler类也是一个函数式接口,仅有一个方法:rejectedExecution(Runnable r, ThreadPoolExecutor executor)。

java线程池默认提供了以下4种拒绝策略:


AbortPolicy

该策略是线程池的默认策略,对于新的任务,直接拒绝执行并且抛出异常。

注:使用该策略时,建议在调用execute方法的地方使用try-catch捕获该异常,否则异常抛出,后续任务无法继续添加到线程池中。

 

 


CallerRunsPolicy

该策略会使用调用线程池的Thread线程处理任务。

 

 


DiscardOldestPolicy

该策略会将最早加入任务队列的任务移除,同时将新的任务添加到任务队列。

 

 


DiscardPolicy

该策略不做任何处理,默认丢弃任务,并且不会抛出异常。

 

 

此外我们可自定义实现拒绝策略,只要实现RejectedExecutionHandle,重写rejectExecution方法即可。

ThreadPoolExecutor执行线程的方法为execute(Runnable command),传入一个Runnable对象即可。

 


ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor的构造函数有以下4个:

 

 

实际上ScheduledThreadPoolExecutor的构造函数,调用的就是父类ThreadPoolExecutor的构造方法,只不过在初始化的时候显示指定了某些参数。

 

 

 

 

 

ScheduledThreadPoolExecutor主要用于定时/延迟执行线程,主要方法如下:

 

前两个schedule()方法,第一个参数分别对应无返回值(Runnable)和有返回值(Callable)的线程,返回的结果封装在返回类型ScheduledFuture对象中,使用get()方法取得;第二个long类型参数为线程延迟执行的时间值;第三个参数为延迟执行的时间单位。

后两个方法用于定期执行某个线程,这两个方法的参数一模一样,

Runnable command, long initialDelay, long period, TimeUnit unit

分别代表Runnable线程对象、初始化执行延迟、定时执行频率和时间单位。二者的不同之处在于:

scheduleAtFixedRate方法的周期执行计算是从上一个线程刚执行的那个时间段开始,经过period延迟后,会直接执行下一个线程,若上一个线程未执行完毕,则会等待其执行完毕后立刻执行(此时已经超出了period延迟,所以会立刻执行)。该方法优先保证任务执行的频率;

scheduleWithFixedDelay方法的周期执行计算是从上一个线程执行完毕后开始,经过period延迟后再执行下一个线程,换句话说,调用scheduleWithFixedDelay方法,定时执行的每个线程之间的时间间隔为period。该方法优先保证任务执行的间隔。

 


四、线程池的工作流程

 

当一个任务通过execute(Runnable)方法欲添加到线程池时,分为以下几个阶段:


第一阶段(判断核心线程数)

此时线程池中的线程数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也会创建新的线程来处理被添加的任务,直到线程数量到达corePoolSize,即率先创建所有的核心线程;

第一阶段过后,此时线程池的线程数量从初始化的0逐步增加至上限corePoolSize,等待队列中元素数量为0;

 


第二阶段(判断等待队列数)

此时线程池中的线程数量等于 corePoolSize,若等待队列 workQueue未满,那么任务将被放入等待队列,直到任务数量到达等待队列的最大size;

第二阶段过后,此时线程池的线程数量保持不变,依旧是corePoolSize,等待队列中元素数量从0逐步增加至上限(如果为有界队列的话);

 


第三阶段(判断最大线程数

此时线程池中的线程数量等于corePoolSize,缓冲队列workQueue满,若线程池中的线程数量小于maximumPoolSize,则创建新的线程来处理被添加的任务。

第三阶段过后,此时线程池的线程数量从corePoolSize逐步增加至上限maximumPoolSize,等待队列元素基本维持在上限(实际中因为任务处理完成,队列的元素数量会不断变化,这里假设任务处理速度慢);

 


第四阶段(执行拒绝策略)

此时线程池中的线程数量等于maximumPoolSize,缓冲队列workQueue满,程序将通过 handler所指定的拒绝策略来处理此任务。

 

因此可以看出处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,若三者都满了,则使用handler处理被拒绝的任务。 

 


五、Executors工具类

Java默认提供了一个创建线程池的工具类Executors,使用该类,可以很便捷地创建所需的各式各样线程池。Executors常用创建线程池的方法如下:

 

实际上Executors的这些静态方法,调用的还是ThreadPoolExecutor的构造函数,区分在与参数的不同,如等待队列、拒绝策略等。

注:不建议使用Executors创建线程池。原因有以下几点:

1、过度依赖这样的线程池创建方式,不利于开发者对于线程池的核心参数理解;

2、OOM风险:

无界队列,队列大小为Integer.MAX_VALUE,过多任务的堆积可能会引起OOM;

 

线程数无限,maximumPoolSize为Integer.MAX_VALUE,过多线程的启动可能会引起OOM。

 

 

 

 



推荐阅读
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
卡哇伊--欣欣_749
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有