Java-线程池技术

一、线程池简介

线程池是一种池化的思想,是将一些共同资源放到池中进行管理和使用,从而避免大量的创建销毁带来的资源浪费等问题,线程池主要优点体现在:

  1. 降低资源消耗:普通线程创建执行完任务之后即被销毁,下次执行任务又创建新的线程,而线程池可以管理线程,多个线程可以重复使用,从而避免了重复创建以及销毁线程带来的资源消耗
  2. 提高响应速度:通过直接创建线程的方式当有请求过来时,需要浪费一部分时间去创建线程,使用线程池则可以直接使用现成的线程执行任务,从而提高了响应速度
  3. 提高线程的可管理性(系统资源、系统稳定性):将线程统一进行管理有利于系统资源的统一管理,同时也增加了系统的稳定性,避免一次性创建过多的线程
  4. 将创建销毁放到系统空闲时间段进行:使用线程池可以将线程的创建销毁放到相对空闲的时间段进行,例如程序启动时,更大的降低了系统访问的压力

同时使用线程池还可以减小 this 逃逸的风险:线程池执行是延迟的,受当前线程池空闲线程以及任务队列等因素影响;线程池的使用本身并不直接发布对象的引用,而是将任务(通常是实现了Runnable 或 Callable 接口的对象)提交给线程池执行

  • this逃逸:类构造函数在返回实例之前,线程便持有该对象的引用
  • 在类的构造函数中初始化this对象以及属性,但在构造函数之外访问了该this引用,则可能由于构造器还未完全完成或者指令重排序的原因就可能会访问到该 this 还未被初始化的属性,导致出现空指针的错误

二、线程池参数

  • corePoolSize : 核心线程数量;任务队列未达到队列容量时,最大可以同时运行的线程数量。
    • 默认核心线程是来一个新任务且无其它空闲核心线程创建一个直到达到上限,也可设置线程池启动时提前创建并启动所有核心线程
    • 执行完成后会检查队列中是否有等待任务并执行
    • 已创建完成的核心线程在执行完任务之后不销毁,等待下一次执行任务。但也可设置销毁核心线程
    • 核心线程执行完当前任务后,如果有新任务或者队列中有任务则继续执行任务
    • 每一次执行完核心线程,会保留线程的结构、状态和数据,进行等待新任务,当有新任务会保证在执行新任务前变成可重用状态即清理旧的相关数据
  • maximumPoolSize : 最大线程数;任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
  • workQueue : 任务队列;新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中
    • 队列未满但核心线程已满时,新任务进来会直接放入队列中,核心线程空闲后会从队列中获取任务执行,获取的方式依照队列设置(通常是先进先出)
    • 队列满但最大线程池未满时再有任务来则会创建临时线程执行新进来的任务,执行完后会检查队列中是否有等待任务并执行
    • 队列满且已到达最大线程则会采用拒绝策略
    • 任务队列:LinkedBlockingQueue(无界队列);SynchronousQueue(同步队列);DelayedWorkQueue(延迟阻塞队列)
  • keepAliveTime : 线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,多余的空闲线程即临时线程不会立即销毁,而是会等待,直到等待的空闲时间超过了 keepAliveTime 才会被回收销毁,线程池回收线程时,会对核心线程和非核心线程一视同仁,直到线程池中线程的数量等于 corePoolSize ,回收过程才会停止
  • unit : keepAliveTime 参数的时间单位
  • threadFactory:线程工厂,可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  • handler拒绝策略:当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略
    • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException 来拒绝新任务的处理(默认)
    • ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用 execute 方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略,提供可伸缩队列
    • ThreadPoolExecutor.DiscardPolicy 不处理新任务,直接丢弃掉
    • ThreadPoolExecutor.DiscardOldestPolicy 此策略将丢弃最早的未处理的任务请求
  • execute:用于提交线程,这个是通用的接口方法。在这个方法里主要实现的就是,当前提交的线程是加入到worker、队列还是放弃
  • addWorker:主要是类 Worker 的具体操作,创建并执行线程。这里还包括了 getTask() 方法,也就是从队列中不断的获取未被执行的线程

线程池执行任务的顺序:

三、线程池核心类

1. Executor

线程池所有类都会实现 Executor 接口,Executor 接口中只定义了一个 execute 方法

2. ExecutorService

ExecutorService接口中定义了 shutdown 及 submit 等方法

shutdown 和 submit方法的区别:

  1. execute方法没有返回值,submit 可以使用 Future 类接收返回值,通过这个 Future 对象可以判断任务是否执行成功,并获取任务的返回值(get() 方法会阻塞当前线程直到任务完成,get(long timeout, TimeUnit unit) 多了一个超时时间,如果在 timeout 时间内任务还没有执行完,就会抛出 TimeoutException),可以使用 cancel 方法取消任务等
  2. execute 通常用于执行 Runable 线程,submit 可以提交 Runnable 或 Callable 任务
  3. submit 方法中如果抛出异常的话,异常会被封装到 Fature 返回对象中,可以使用get方法获取异常内容,而对于 execute 方法抛出异常的话默认处理方式是打印出堆栈信息并停止线程,也可以使用 UncaughtExceptionHandler 或者 afterExecute() 处理异常

3. ThreadPoolExecutor

ThreadPoolExecutor是最常使用也是最核心的类,ThreadPoolExecutor提供了四个不同参数的构造方法,其中最主要的构造方法如下,其他构造方法也是调用的该构造方法,只是对于部分参数进行了默认值初始化

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
                          TimeUnit unit, BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 || maximumPoolSize <= 0 || 
        maximumPoolSize < corePoolSize || keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ? null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

4. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 类继承了 ThreadPoolExecutor 类,并且实现了ScheduledExecutorService 接口

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService{}

5. Executors

Executors类中封装了多种线程池,可以直接通过该类创建对应线程,这里介绍4种常见的功能线程池

封装的线程池内部都是通过调用 ThreadPoolExecutor 或者 ScheduledThreadPoolExecutor 类进行初始化的,只是传入的线程池参数不同,所以就形成了不同功能的线程

  • FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

从代码中可以看出 FixedThreadPool 线程池是传入一个数值,并且该数值作为初始化的线程的核心线程数和最大线程数,表示该线程池是一个核心线程数等于最大线程数的线程池,即不存在临时线程

由于使用的队列为 LinkedBlockingQueue 无界队列,所以表示该线程池的等待队列永远不会被放满,接收到的任务数量也是不被限制的,可能会有OOM内存溢出的风险,并且只会创建核心线程,不会创建临时线程执行任务

  • SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

从代码中可以看出 SingleThreadExecutor 线程池没有传入任何参数,而核心线程数等于最大线程数为1,同时最多只能有一个线程执行任务

同时由于使用的队列也是 LinkedBlockingQueue 无界队列,所以表示该线程池的等待队列永远不会被放满,接收到的任务数量也是不被限制的,可能会有OOM内存溢出的风险,并且只会创建核心线程,不会创建临时线程执行任务

  • CachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

从代码中可以看出 CachedThreadPool 线程池没有传入任何参数,而核心线程数设置为0,最大线程数设置为了int最大值,表示该线程池没有核心线程,只能创建临时线程执行任务,且最大线程数为最大值,可能导致创建大量的线程,导致出现OOM内存溢出的风险

CachedThreadPool 线程池使用的 SynchronousQueue 同步队列容量为空,所以核心线程满时会直接创建大量的临时线程执行任务,从而导致出现OOM内存溢出的风险

  • ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

该线程池是通过 ScheduledThreadPoolExecutor 类创建的,而 ScheduledThreadPoolExecutor类 也是通过调用 ThreadPoolExecutor 类生成的线程池

ScheduledThreadPool 线程池使用的队列为 DelayedWorkQueue 延迟阻塞队列,会按照延迟的时间长短对任务进行排序,每次会将队列中执行时间最靠前的任务取出执行,是一个动态队列,队列元素满时会自动扩容,所以只会创建核心线程,不会创建临时线程

注意:《阿里巴巴 Java 开发手册》强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式可以更好的掌握线程池的运行规则,制定合适参数的线程池,避免资源耗尽的风险

四、线程池实例

  1. 使用 @Bean("name") 对 ThreadPoolExecutor 进行bean注册
  2. 使用构造方法传入线程池参数进行 bean 实例注册
  3. 需要通过名称匹配到对应 bean,使用 @Autowired 搭配 @Qualifier("name") 或者 @Resource(name = "name") 注入(如果程序中只需要一个线程池的话那么可以直接使用Autowired 注解通过类型 bean 注入)
@Component
public class ThreadPoolBean {
    @Bean("labelWsThreadPool") 
    public ThreadPoolExecutor createThreadPoolExecutor() {
        return new ThreadPoolExecutor(10, 40, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5000), new ThreadPoolExecutor.CallerRunsPolicy());
    }
 
    @Autowired   //线程池bean注入
    @Qualifier("CustomerThreadPool")
    //@Resource(name = "CustomerThreadPool")
    private ThreadPoolExecutor threadPoolExecutor;
}
  1. 或者使用工厂 Factory 类定义一个 ExecutorService 线程池,并定义初始化方法 init() 在该方法中初始化线程池,且在系统启动时调用该方法实现初始化,并提供 execute 方法调用线程池。由于方法是定义的 static 类型所以可以直接通过类名调用
public class ThreadFactory {
	private static ExecutorService businessExecutor = null;  //业务处理线程池 

	public static void init(){
		LOG.info("初始化线程池");
		Iexecutor = new ThreadPoolExecutor(9, 40, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(5000),new ThreadPoolExecutor.CallerRunsPolicy());
		LOG.info("初始化线程池结束");
	}
	
	public static void excute(Runnable run){
		businessExecutor.execute(run);
	}

	public static Future submit(Callable call){
		return businessExecutor.submit(call);
	}
}
  1. 实现一个线程 Thread,线程 Thread 通过实现 run 方法自定义方法体,如果有参数传入,可在自定义 Thread 类类中定义全局变量并通过构造函数传入赋值
    1. 创建线程的四种方式,将创建的线程通过 submit() 或者 execute() 的参数传入执行:
    2. extends Thread
    3. executor.execute(new Runnable() { public void run() { } })
    4. implements Callable<String>,具有返回值Future
    5. implements Runnable
//@AllArgsConstructor,如果有属性不需要传入初始化,那么可自定义构造方法,将需要的参数放入构造方法
//@Component  //如果该类为Spring管理的bean,且该类中只有一个构造方法,那么Spring尝试将构造函数的参数注入bean,未找到bean则会报错
public class MyThread extends Thread{
    private List<String> phoneNoList;
    private int num = 10000;

    public PhoneNoExhaustionThread(List<String> phoneNoList) {
        this.phoneNoList = phoneNoList;
    }

    @Override
    public void run(){
        //执行任务
    }
}
  1. 再调用 execute 方法传入实现线程的对象即可
threadPoolExecutor.execute(new MyThread(phoneNoList));   //通过bean注入实现

ThreadFactory.excute(new MyThread(phoneNoList));   //通过工厂类实现

五、线程池使用方式

  1. 执行调用线程池,主线程后续执行其他步骤,主线程和线程池中的任务并发执行,取决于CPU和核数以及CPU时间调度等因素,主线程可直接执行程序返回操作
  2. 使用 CountDownLatch 计数器等工具类等待线程池中的对应任务执行完毕,可用于多个任务并发执行,但主线程需要等待所有任务执行完毕,主线程依赖多个线程的执行结果,多个线程从串行执行变成并行执行

六、关闭线程池

可在使用完线程池后选择合适的方式关闭线程池,也可通过工厂模式封装关闭线程池,后续在程序关闭前的步骤中对线程池进行关闭

  1. shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕
  2. shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/900084.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】类和对象(附题)

目录 一、类的定义 1.1.类定义格式 1.2.访问限定符 1.3.类域 二、实例化 2.1.实例化概念 2.2.对象大小 三、this指针 附加题&#xff1a;&#xff08;增进对this指针的理解&#xff09; 1.下面程序编译运行结果是&#xff08;&#xff09; 2.下面程序编译运行结果是&…

linux下gpio模拟spi时序

目录 前言一、配置内容二、驱动代码实现三、总结 前言 本笔记总结linux下使用gpio模拟spi时序的方法&#xff0c;基于arm64架构的一个SOC&#xff0c;linux内核版本为linux5.10.xxx&#xff0c;以驱动三线spi(时钟线sclk&#xff0c;片选cs&#xff0c;sdata数据读和写使用同一…

antv g6问题处理汇总

关于自定义边时&#xff0c;箭头始终没出现的问题处理 问题&#xff1a; 问题对应的代码 解决方法&#xff1a;将箭头的偏移量调整y坐标 完整代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8" /><title&…

使用vue+kkFileview组件实现各种类型文件预览

关于kkFileView 【参考】&#xff1a;https://kkfileview.keking.cn/zh-cn/docs/home.html 文档在线预览项目解决方案&#xff0c;项目使用流行的spring boot搭建&#xff0c;易上手和部署。万能的文件预览开源项目&#xff0c;基本支持主流文档格式预览 本项目介绍 项目使用…

无忧树闪耀2024中国防水展:智能新材料,引领新赛道!

2024年10月16日&#xff0c;上海无忧树新材料科技有限公司在上海国家会展中心5.2号馆5103展位&#xff0c;成功亮相2024中国国际屋面和建筑防水技术展览会。作为新材料科技领域的佼佼者&#xff0c;无忧树以创新的技术、卓越的产品和专业的服务&#xff0c;赢得了现场观众的广泛…

COVON全意卫生巾,轻薄透气,绵柔速干,马来西亚热销中

随着女性健康意识的提高&#xff0c;卫生巾作为女性日常生活中的必需品&#xff0c;其品质和舒适度越来越受到关注。今天&#xff0c;我们要为大家介绍一款来自马来西亚热销的卫生巾——COVON全意卫生巾&#xff0c;以其轻薄透气、绵柔速干的特点&#xff0c;赢得了广大女性的喜…

【有啥问啥】视频插帧算法技术原理详解

视频插帧算法技术原理详解 引言 视频插帧&#xff08;Video Interpolation&#xff09;技术&#xff0c;作为计算机视觉领域的一项重要应用&#xff0c;旨在通过算法手段在已有的视频帧之间插入额外的帧&#xff0c;从而提升视频的帧率&#xff0c;使其看起来更加流畅。这一技…

oracle19c的k8s部署

前提条件 1、首先要有一个oracle 账号 2、需要一台能连接网络并安装docker的机器用Oracle账号登录Home 点击database 跳转到下一个页面 记得一定sign in ,否则无法拉取镜像 docker pull container-registry.oracle.com/database/enterprise:latest 执行拉取后使用镜像进行部…

基于Ubuntu24.04,下载并编译Android12系统源码 (二)

1. 前言 上篇文章&#xff0c;我们基于Ubuntu24.04&#xff0c;已经成功下载下来了Android12的源码&#xff0c;这篇文章我们会接着上文&#xff0c;基于Ubuntu24.04来编译Android源码。 2. 编译源码 2.1 了解源码编译的名词 Makefile &#xff1a; Android平台的一个编译系…

Diffusion Probabilistic Models for 3D Point Cloud Generation——点云论文阅读(8)

此内容是论文总结&#xff0c;重点看思路&#xff01;&#xff01; 文章概述 该文献介绍了一种用于3D点云生成的概率模型。点云是表示3D物体和场景的常用方式&#xff0c;但由于其不规则的采样模式&#xff0c;与图像相比&#xff0c;点云生成更具挑战性。现有方法如GANs、流…

Flutter通过showDialog实现下拉筛选菜单效果

一、效果图 二、 实现方式 获取固定在顶部筛选头部Widget在屏幕上的位置和它的高度在弹窗中通过获取到的高度进行内容显示区域定位巧用AnimatedContainer组件实现下拉动画效果最后在底部加上黑色蒙层 unawaited(showDialog(context: context,useSafeArea: false,barrierColor…

Golang | Leetcode Golang题解之第503题下一个更大元素II

题目&#xff1a; 题解&#xff1a; func nextGreaterElements(nums []int) []int {n : len(nums)ans : make([]int, n)for i : range ans {ans[i] -1}stack : []int{}for i : 0; i < n*2-1; i {for len(stack) > 0 && nums[stack[len(stack)-1]] < nums[i%…

vue2-render:vue2项目使用render / 基础使用

一、本文内容 本文内容记录render常用的一些属性和方法的配置&#xff0c;以作参考 export default { data() {return { modelValue: ,key: 0,}; }, render(h) { return h(div, [ h(input, {class: input,attrs: { type: text }, key: this.key,props: { value: thi…

【MATLAB代码】EKF和CDKF的对比

目录 主要特点 应用场景 运行结果展示 本MATLAB程序实现了扩展卡尔曼滤波&#xff08;EKF&#xff09;与协方差差分卡尔曼滤波&#xff08;CDKF&#xff09;在三维状态估计中的效果对比&#xff0c;为需要高精度定位与动态系统分析的用户提供了一种实用工具。通过直观的结果…

CenterTrack算法详解

背景&#xff1a; 早期追踪器在缺乏强的低水平线索下&#xff0c;容易失败检测后跟踪的模型依赖于检测器&#xff0c;且需要一个单独的阶段匹配关联策略的时间长 简介&#xff1a; 基于点的跟踪思想&#xff0c;通过预测目标的中心点来进行跟踪&#xff0c;同时实现检测与跟…

【开源免费】基于SpringBoot+Vue.JS蜗牛兼职平台 (JAVA毕业设计)

本文项目编号 T 034 &#xff0c;文末自助获取源码 \color{red}{T034&#xff0c;文末自助获取源码} T034&#xff0c;文末自助获取源码 目录 一、系统介绍1.1 平台架构1.2 管理后台1.3 用户网页端1.4 技术特点 二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景…

SSCI/SCI/EI/Scopus/期刊合集,周期短,快速发表,见刊快!

【期刊合集专场】本期为计算机、材料、工程技术、医学、社科经管、农林科学类领域的SCI&SSCI、Scopus、EI&#xff0c;涵盖&#xff1a;人工智能、纳米材料、工程材料、肿瘤学、管理学、农作物保护等征稿方向&#xff01; 期刊推荐一、Intelligence & Robotics 学科领域…

【C++篇】继承之巅:超越法则束缚,领略面向对象的至臻智慧

文章目录 C 继承详解&#xff1a;虚拟继承与进阶实战前言第一章&#xff1a;继承与友元、静态成员1.1 继承与友元1.1.1 友元函数的定义 1.2 继承与静态成员1.2.1 静态成员的继承与访问 第二章&#xff1a;复杂的菱形继承及虚拟继承2.1 菱形继承问题2.1.1 菱形继承的基本结构 2.…

「漏洞复现」东胜物流软件 GetProParentModuTreeList SQL注入漏洞

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

数据结构——树——二叉树——大小堆

目录 1>>导言 2>>树 2.1>>树的相关术语 2.2>>树的表示和应用场景 3>>二叉树 3.1>>完全二叉树 3.2>>大小根堆 4>>结语 1>>导言 上篇小编将队列的内容给大家讲完了&#xff0c;这篇要步入新的篇章&#xff0c;请宝…