Java线程池(更新中)

  1.线程池介绍

顾名思义,线程池就是管理一系列线程的资源池,其提供了一种限制和管理线程资源的方式。每个线程池还维护一些基本统计信息,例如已完成任务的数量。

总结一下使用线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池一般用于执行多个不相关联的耗时任务,没有多线程的情况下,任务顺序执行,使用了线程池的话可让多个不相关联的任务同时执行。

2.Executor框架介绍

Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Threadstart 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。

this逃逸指的是在对象的构造过程中,当对象还没有完全初始化完成时(构造函数执行完毕),就将该对象的引用传递给了外部环境。这种情况可能会导致对该对象的访问出现不一致性或者线程安全问题。

具体来说,当一个线程在调用对象的构造函数创建对象实例时,如果对象引用在构造函数执行完成之前就被传递给了另一个线程,那么另一个线程可能会访问到尚未完成初始化的对象,这就是this逃逸。

this逃逸可能导致的问题包括:对象状态不一致、线程安全问题、空指针异常等。因此,在编写Java代码时,我们应该避免在构造函数中将this引用传递给外部环境,以确保对象在完全初始化后才能被其他线程访问。

Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。

Executor 框架结构主要由三大部分组成:

1、任务(Runnable /Callable)

执行任务需要实现的 Runnable 接口Callable接口Runnable 接口Callable 接口 实现类都可以被 ThreadPoolExecutorScheduledThreadPoolExecutor 执行。

2、任务的执行(Executor)

如下图所示,包括任务执行机制的核心接口 Executor ,以及继承自 Executor 接口的 ExecutorService 接口。ThreadPoolExecutorScheduledThreadPoolExecutor 这两个关键类实现了 ExecutorService 接口。

这里提了很多底层的类关系,但是,实际上我们需要更多关注的是 ThreadPoolExecutor 这个类,这个类在我们实际使用线程池的过程中,使用频率还是非常高的。

注意: 通过查看 ScheduledThreadPoolExecutor 源代码我们发现 ScheduledThreadPoolExecutor 实际上是继承了 ThreadPoolExecutor 并实现了 ScheduledExecutorService ,而 ScheduledExecutorService 又实现了 ExecutorService,正如我们上面给出的类关系图显示的一样。

ThreadPoolExecutor 类描述:

//AbstractExecutorService实现了ExecutorService接口
public class ThreadPoolExecutor extends AbstractExecutorService

ScheduledThreadPoolExecutor 类描述:

//ScheduledExecutorService继承ExecutorService接口
public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService

3、异步计算的结果(Future)

Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。

当我们把 Runnable接口Callable 接口 的实现类提交给 ThreadPoolExecutorScheduledThreadPoolExecutor 执行。(调用 submit() 方法时会返回一个 FutureTask 对象)

Executor 框架的使用示意图

Executor 框架的使用示意图

  • 1.主线程首先要创建实现 Runnable 或者 Callable 接口的任务对象。
  • 2.把创建完成的实现 Runnable/Callable接口的对象直接交给 ExecutorService 执行: ExecutorService.execute(Runnable command))或者也可以把 Runnable 对象或Callable 对象提交给 ExecutorService 执行(ExecutorService.submit(Runnable task)ExecutorService.submit(Callable <T> task))。
  • 3.如果执行 ExecutorService.submit(…)ExecutorService 将返回一个实现Future接口的对象(我们刚刚也提到过了执行 execute()方法和 submit()方法的区别,submit()会返回一个 FutureTask 对象)。由于FutureTask 实现了 Runnable,我们也可以创建 FutureTask,然后直接交给 ExecutorService 执行。
  • 4.最后,主线程可以执行 FutureTask.get()方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

3.ThreadPoolExecutor类介绍(重要)

线程池实现类 ThreadPoolExecutorExecutor 框架最核心的类。

1.线程池参数分析

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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

下面这些参数非常重要,在后面使用线程池的过程中你一定会用到!所以,务必记清楚。

ThreadPoolExecutor 3 个最重要的参数:

  • corePoolSize : 任务队列未达到队列容量时,最大可以同时运行的线程数量。
  • maximumPoolSize : 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  • workQueue: 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

ThreadPoolExecutor其他常见参数 :

  • keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁。
  • unit : keepAliveTime 参数的时间单位。
  • threadFactory :executor 创建新线程的时候会用到。
  • handler :拒绝策略(后面会单独详细介绍一下)。

下面这张图可以加深你对线程池中各个参数的相互关系的理解

线程池各个参数的关系

线程池各个参数的关系

ThreadPoolExecutor 拒绝策略定义:

如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolExecutor 定义一些策略:

  • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
  • ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。

        举个例子:Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor 的构造函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 拒绝策略来配置线程池的时候,默认使用的是 AbortPolicy。在这种拒绝策略下,如果队列满了,ThreadPoolExecutor 将抛出 RejectedExecutionException 异常来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。如果不想丢弃任务的话,可以使用CallerRunsPolicyCallerRunsPolicy 和其他的几个策略不同,它既不会抛弃任务,也不会抛出异常,而是将任务回退给调用者,使用调用者的线程来执行任务。

public static class CallerRunsPolicy implements RejectedExecutionHandler {

        public CallerRunsPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                // 直接主线程执行,而不是线程池中的线程执行
                r.run();
            }
        }
    }

2.创建线程池的两种方式

方式一:通过ThreadPoolExecutor构造函数来创建(推荐)。

方式二:通过 Executor 框架的工具类 Executors 来创建。

Executors工具类提供的创建线程池的方法如下图所示:

可以看出,通过Executors工具类可以创建多种类型的线程池,包括:

  • FixedThreadPool:固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
  • SingleThreadExecutor: 只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
  • CachedThreadPool: 可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
  • ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池。

《阿里巴巴 Java 开发手册》强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

Executors 返回线程池对象的弊端如下(后文会详细介绍到):

  • FixedThreadPoolSingleThreadExecutor:使用的是无界的 LinkedBlockingQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
  • CachedThreadPool:使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE ,如果任务数量过多且执行速度较慢,可能会创建大量的线程,从而导致 OOM。
  • ScheduledThreadPoolSingleThreadScheduledExecutor:使用的无界的延迟阻塞队列DelayedWorkQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
// 无界队列 LinkedBlockingQueue
public static ExecutorService newFixedThreadPool(int nThreads) {

    return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());

}

// 无界队列 LinkedBlockingQueue
public static ExecutorService newSingleThreadExecutor() {

    return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));

}

// 同步队列 SynchronousQueue,没有容量,最大线程数是 Integer.MAX_VALUE`
public static ExecutorService newCachedThreadPool() {

    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());

}

// DelayedWorkQueue(延迟阻塞队列)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

3.线程池常用的阻塞队列

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

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

相关文章

猎头告诉你正确的“离职流程”

往期热门文章&#xff1a; 1&#xff0c;史上最全猎头技能资料&#xff0c;独家最新放送 2&#xff0c;互联网大厂java面试题知识库&#xff08;100万字&#xff09; 3&#xff0c;一线互联网大数据面试题知识库&#xff08;100万字&#xff09; 4&#xff0c;中国猎头公司排行…

SQL优化详解

目录 插入数据 insert的优化&#xff08;少量数据&#xff09; 批量插入 手动事务提交 主键顺序插入 插入大量数据 主键优化 数据组织方式&#xff1a; 页分裂&#xff1a; 主键顺序插入的方式&#xff1a; 主键乱序插入&#xff1a; 页合并&#xff1a; 主键设计…

HTML5/CSS3粒子效果进度条 超炫酷进度条动画源码

特效介绍 之前我已经分享了几款效果很不错的CSS3进度条插件&#xff0c;比如CSS3 Loading进度条加载动画特效、CSS3 3D进度条按钮 18款精美样式。今天我再来分享一款很有特色的HTML5/CSS3进度条应用。这款进度条插件在播放进度过程中出现粒子效果&#xff0c;就像一些小颗粒从…

二本生如何从大一准备考研!?保姆级全攻略

如果是二本大学&#xff0c;那考研确实是一个很好的机会 如果大家就有考研的打算&#xff0c;那就好好学习&#xff0c;好好学习英语&#xff0c;数学&#xff08;理工科&#xff09;和专业课&#xff0c;这些课程在考研的时候是肯定会考的 特别是英语和数学&#xff08;理工…

每日Attention学习5——Multi-Scale Channel Attention Module

模块出处 [link] [code] [WACV 21] Attentional Feature Fusion 模块名称 Multi-Scale Channel Attention Module (MS-CAM) 模块作用 通道注意力 模块结构 模块代码 import torch import torch.nn as nnclass MS_CAM(nn.Module):def __init__(self, channels64, r4):super(…

五一开始内卷前端,如何迅速的一个月内找到工作!

写在前面 五一过了代表新的一年不知不觉过了半年了&#xff0c;各位工作找到怎么样&#xff0c;有没有在工作中遇到解决不了的问题&#xff0c;这些问题后面怎么处理了呢&#xff1f; hello大家好&#xff0c;我又又又来了&#xff0c;今天纯干货&#xff0c;上班的朋友适当摸…

【SAP ME 39】SAP ME WebService超时时间设置

禁止废话&#xff0c;直接上图&#xff01;&#xff01;&#xff01; SAP技术官方说明

Dark Reader:夜间模式,启动!

名人说&#xff1a;一点浩然气&#xff0c;千里快哉风。 ——苏轼 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、介绍二、下载安装1、Chrome应用商店&#xff08;需科学&#xff09;2、第三方直链下载 三、使…

深入探索数据链路层:网络通信的基石

⭐小白苦学IT的博客主页⭐ ⭐初学者必看&#xff1a;Linux操作系统入门⭐ ⭐代码仓库&#xff1a;Linux代码仓库⭐ ❤关注我一起讨论和学习Linux系统❤ 前言 在网络通信的宏伟世界中&#xff0c;数据链路层扮演着至关重要的角色。它位于物理层和网络层之间&#xff0c;不仅直接…

HuggingFace烧钱做了一大批实验,揭示多模态大模型哪些trick真正有效

构建多模态大模型时有很多有效的trick&#xff0c;如采用交叉注意力机制融合图像信息到语言模型中&#xff0c;或直接将图像隐藏状态序列与文本嵌入序列结合输入至语言模型。 但是这些trick为什么有效&#xff0c;其计算效率如何&#xff0c;往往解释得很粗略或者或者缺乏充分…

C++ Builder XE EnumWindowsProc遍历所有窗口的名称

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { // 这里可以添加你的处理逻辑 // 例如&#xff0c;将句柄添加到列表中或者其他操作 // 这里我们仅仅输出到调试窗口 OutputDebugString(L"枚举窗口句柄: "); char windowHandle[10];…

ROS 2边学边练(45)-- 构建一个能动的机器人模型

前言 在上篇中我们搭建了一个机器人模型(其由各个关节&#xff08;joint&#xff09;和连杆&#xff08;link&#xff09;组成)&#xff0c;此篇我们会通过设置关节类型来实现机器人的活动。 在ROS中&#xff0c;关节一般有无限旋转&#xff08;continuous&#xff09;,有限旋转…

【每日力扣】98. 验证二叉搜索树 与 108. 将有序数组转换为二叉搜索树

&#x1f525; 个人主页: 黑洞晓威 &#x1f600;你不必等到非常厉害&#xff0c;才敢开始&#xff0c;你需要开始&#xff0c;才会变的非常厉害 98. 验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&a…

【优选算法】——双指针——15. 三数之和

目录 1.题目 2.解法&#xff08;排序双指针&#xff09;&#xff1a; 算法思路&#xff1a; 3.代码实现 1.题目 15. 三数之和 提示 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足…

【LLM第三篇】名词解释:RLHF——chatgpt的功臣

RLHF (Reinforcement Learning from Human Feedback) &#xff0c;直译为&#xff1a;“来自人类反馈的强化学习”。RLHF是一种结合了强化学习和人类反馈的机器学习方法&#xff0c;主要用于训练大模型以执行复杂的任务&#xff0c;尤其是当这些任务难以通过传统的奖励函数来精…

重学java 33.API 4.日期相关类

任何事&#xff0c;必作于细&#xff0c;也必成于实 —— 24.5.9 一、Date日期类 1.Date类的介绍 1.概述: 表示特定的瞬间,精确到亳秒 2.常识: a.1000毫秒 1秒 b.时间原点:1970年1月1日 0时0分0秒(UNIX系统起始时间),叫做格林威治时间,在0时区上 c.时区:北京位于东八区,一个时区…

Linux 操作系统线程1

目录 一、线程 1.1线程的基本概念 1.2 线程相关的API函数 1.2.1 线程的创建 1.2.2 线程退出 1.2.3 线程等待函数 1.2.4 获取线程ID 1.2.5 线程取消 1.2.6 线程的清理函数 一、线程 1.1线程的基本概念 线程是属于进程&#xff1b;一个进程可以有多个线程&#xff…

salmon使用体验

文章目录 salmon转录本定量brief模式一&#xff1a;fastq作为输入文件需要特别注意得地方 模式二&#xff1a; bam文件作为输入 salmon转录本定量 brief 第一点是&#xff0c;通常说的转录组分析其中有一项是转录本定量&#xff0c;这是一个很trick的说话&#xff0c;说成定量…

深度学习——前馈全连接神经网络(鸢尾花)

前馈全连接神经网络对鸢尾花数据集进行分类 1.导入所需要的包2.打印训练集和测试集二维数组3.定义模型4.打印模型信息5.权重和偏执6.编译网络和训练网络7.打印二维数据表格8.绘制图像9.查看准确率 1.鸢尾花数据集可以用 from sklearn.datasets import load_iris 方式获取&#…

医院预约挂号|基于Springboot+vue的医院预约挂号系统小程序的设计与实现(源码+数据库+文档)

医院预约挂号系统小程序 目录 基于Springboot&#xff0b;vue的医院预约挂号系统小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1小程序端 后台功能模块 4.2.1管理员功能 4.2.2医生功能 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选…
最新文章