首页 > 编程语言 >Java 线程池

Java 线程池

时间:2023-10-12 11:00:28浏览次数:53  
标签:Java 队列 创建 池中 任务 线程 ThreadPoolExecutor

目录

线程池

线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。

使用线程池的好处:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。

  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池创建方式

线程池创建的方式:

  • ThreadPoolExecutor 创建(推荐)

    使用线程池的好处是:减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

  • 通过 Executor 框架的工具类 Executors 创建

    我们可以创建多种类型的 ThreadPoolExecutor:

    • FixedThreadPool:该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。

    • SingleThreadExecutor: 该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。

    • CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。初始大小为0。当有新任务提交时,如果当前线程池中没有线程可用,它会创建一个新的线程来处理该任务。如果在一段时间内(默认为60秒)没有新任务提交,核心线程会超时并被销毁,从而缩小线程池的大小。

    • ScheduledThreadPool:该方法返回一个用来在给定的延迟后运行任务或者定期执行任务的线程池

使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

使用 Executors 创建线程池对象的弊端如下:

  • FixedThreadPool 和 SingleThreadExecutor:使用的是无界的 LinkedBlockingQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

  • CachedThreadPool:使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE ,如果任务数量过多且执行速度较慢,可能会创建大量的线程,从而导致 OOM。

  • ScheduledThreadPool 和 SingleThreadScheduledExecutor : 使用的无界的延迟阻塞队列DelayedWorkQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

通过 ThreadPoolExecutor 创建线程池

ThreadPoolExecutor 的构造函数:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize:核心线程数,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去。

  • maximumPoolSize:线程池中的最大线程数量,这个参数会根据 workQueue 任务队列的类型,决定线程池会开辟的最大线程数量。

    任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。

  • keepAliveTime:当线程池中空闲线程数量超过 corePoolSize 时,多余的线程会在多长时间内被销毁。

    线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,多余的空闲线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime 才会被回收销毁,线程池回收线程时,会对核心线程和非核心线程一视同仁,直到线程池中线程的数量等于 corePoolSize ,回收过程才会停止。

  • unit:keepAliveTime 的单位

  • workQueue:工作队列,在任务执行之前用于保存任务的队列。

    可以选择的工作队列:ArrayBlockingQueue、DelayQueue、LinkedBlockingDeque、LinkedBlockingQueue、LinkedTransferQueue、PriorityBlockingQueue、SynchronousQueue

  • threadFactory:线程工厂,用于创建线程,一般用默认即可。

  • handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;

    可以选择的拒绝策略:ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.CallerRunsPolicy、ThreadPoolExecutor.DiscardOldestPolicy、ThreadPoolExecutor.DiscardPolicy

线程池中各个参数的相互关系:

image

ThreadPoolExecutor 的总体设计

ThreadPoolExecutor 的继承关系

ThreadPoolExecutor 的 UML 类图如下:

image

ThreadPoolExecutor 的运行机制

ThreadPoolExecutor 运行机制如下图所示:

image

线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好地缓冲任务,复用线程。

线程池的运行主要分成两部分:任务管理、线程管理。

  • 任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:

    • 直接申请线程执行该任务;

    • 缓冲到队列中等待线程执行;

    • 拒绝该任务。

  • 线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。

ThreadPoolExecutor 生命周期管理

线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量(workerCount),其源码如下:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl 这个 AtomicInteger 类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段,它同时包含两部分的信息:

  • 线程池的运行状态(runState):变量 ctl 的高 3 位保存 runState;

  • 线程池内有效线程的数量(workerCount):变量 ctl 的低 29 位保存 workerCount。

其中,两个变量之间互不干扰,用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。通过阅读线程池源代码也可以发现,经常出现要同时判断线程池运行状态和线程数量的情况。线程池也提供了若干方法去供用户获得线程池当前的运行状态、线程个数。这里都使用的是位运算的方式,相比于基本运算,速度也会快很多。

ThreadPoolExecutor的运行状态有5种,分别为:

运行状态 描述
RUNNING 能接受新提交的任务,并且也能处理阻塞队列中的任务。
SHUTDOWN 关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。
STOP 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。
TIDYING 所有的任务都已终止了,有效线程数(workCount)为 0
TERMINATED 在 terminated() 方法执行完成后,进入该状态

其生命周期流转状态如下:

image

任务执行机制

标签:Java,队列,创建,池中,任务,线程,ThreadPoolExecutor
From: https://www.cnblogs.com/larry1024/p/17759003.html

相关文章

  • Java word文本分词器简单使用
    1、引入依赖<dependency><groupId>org.apdplat</groupId><artifactId>word</artifactId><version>1.2</version></dependency>2、使用@OverridepublicList&l......
  • 87基于java的流浪动物领养系统设计与实现(配套lun文,PPT,可参考做毕业设计)
    本章节给大家带来一个基于java流浪动物领养系统设计与实现,可适用于流浪动物救助及领养管理系统,宠物教学、领养宠物、宠物认领、领养申请、动物认领信息,动物申请认领等等;项目背景科学技术日新月异的如今,计算机在生活各个领域都占有重要的作用,尤其在信息管理方面,在这样的大背景......
  • 炫酷转换:Java实现Excel转换为图片的方法
    摘要:本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。前言在实际开发过程中,经常会有这样的需求:将Excel表格或特定区域转换为图片,以便在其他软件中使用。而在Java开发中,借助于报表插件可以轻松地将工作......
  • 在JavaScript中,最高效的方法来深度克隆一个对象是什么?
    内容来自DOChttps://q.houxu6.top/?s=在JavaScript中,最高效的方法来深度克隆一个对象是什么?将JavaScript对象进行深度克隆的最有效方法是什么?我见过使用obj=eval(uneval(o));,但这是非标准的做法,仅被Firefox支持。我曾尝试过obj=JSON.parse(JSON.stringify(o));,但对效率......
  • java.lang.ClassNotFoundException org.apache.ibatis.io.Resources问题的解决
    问题描述时隔好久,再次使用mybatis框架写管理系统,运行时出现了这个问题;问题解决我看着我也导入了相关的依赖,然后就发现,原来是没有放入到libaray里面,只需要这么做就能搞定啦:打开项目里面的这里:将右边的需要的包双击即可加入进去啦!再次运行就不会报错啦~~......
  • JavaScript Library – YouTube Embedded、YouTube Player API、YouTube Data API
    YouTube EmbedVideo参考: Embedvideos&playlists它和 GoogleMapsEmbed 类似,是通过iframe完成的。<iframewidth="800"style="aspect-ratio:16/9"src="https://www.youtube.com/embed/vEZCoe9GJFk"title="粉色海洋"......
  • java 四种内部类
    四种内部类基本介绍:一个类的内部又完整嵌套了了另一个类结构,被嵌套的类称为内部类属性,方法,构造器,代码块,内部类类的五大成员内部类是学习的难点,同事也是重点,后面看底层源码时,有大量得到内部类1.定义在外部类的局部位置上(比如方法内):局部内部类(有类名)匿名内部类(没有类名......
  • ndk开发之native层访问java层
    一.native层访问java层的成员变量java层的成员变量可以分为实例变量和静态变量,不过他们的访问方法比较类似,可以分为以下三步:获取java类对应的jclass对象获取需要访问的成员变量的jfieldID根据需要访问的变量的类型,调用setXXXField()/getXXXField()方法来设置/获取成员变......
  • 深入浅出JavaScript闭包
    什么是JS闭包?JS闭包是一个难点也是JS的特色,是JS的高级特性。首先我们知道JS运行函数的时候会在内存中开辟一个存储空间,会把函数体内的代码当作字符串一摸一样的放在这个空间中,把这个空间地址赋值给函数名(变量名),当我们调用函数的时候会根据地址找到这个储存空间,然后执行储存空......
  • javaweb第7章源码
    javaweb第7章源码:下载链接:https://wwpv.lanzoue.com/iurOS1bijocb文件结构:CHAPTER07│.classpath│.project│├─.settings│.jsdtscope│org.eclipse.jdt.core.prefs│org.eclipse.wst.common.component│org.eclipse.wst.common.proje......