【Java】多线程前置知识 初识Thread

news2024/9/21 12:42:53

多线程前置知识 & 初识Thread

  • 冯诺依曼体系结构
    • 初步认识
    • 存储设备
    • CPU
    • 指令
  • 操作系统
    • 初识操作系统
    • 内核态和用户态
  • 进程/任务
    • 进程是什么
    • 进程的管理
    • 进程的调度
    • 虚拟内存地址
    • 进程间的通信
  • 线程
    • 线程的出现
    • 线程是什么
    • 线程可能出现的问题
    • 线程与进程的联系和区别
  • 协程
  • 初识Thread类
    • Thread类是什么
    • Thread初步使用

冯诺依曼体系结构

初步认识

冯诺依曼体系结构是当今计算机的基本结构, 当今的大多数计算机都是能够基本符合这个结构的, 它的大体图示如下

在这里插入图片描述

它主要分为了输入设备, 存储器, 输出设备以及 CPU 这几大块, 其中我们接下来主要就需要去了解一下存储器和 CPU 相关的知识.

而输入和输出的设备, 我们就可以简单理解为像鼠标, 键盘这样的把我们操作的信息提供给计算机的, 就属于是输入设备. 而像屏幕, 音响这样的把信息传输出来的, 就是输出设备.

接下来我们就来简单了解一下另外两个部分

存储设备

现在来说, 存储设备一般有两种, 一个叫做内存, 一个叫做外存. 例如我们平常买一个手机, 可能看到说它的大小是 1T, 512G 这样的, 它描述的就是硬盘的大小, 此时就属于是一种外存. 而那些什么运行内存 8G, 4G 这样的, 就属于是内存.

那么这两个东西有什么区别呢? 其实我们它们的区别也非常简单, 通过两句话就可以概括完

  1. 内存, 访问速度相对较快, 空间相对较小, 成本较高, 断电后数据丢失
  2. 外存, 访问速度相对较慢, 空间相对较大, 成本较低, 断电后数据保存

平常, 我们运行的一些程序, 例如 QQ, 微信, 浏览器什么的, 它就是运行在内存上面的, 此时如果我们去把电脑的电给拔了, 那么此时去重新开机, 会发现这些程序都没了, 需要重新开启.

而像那些文件, 比如 word, pdf 这样的, 假如我们把它存储到了一个文件夹后, 那么无论我怎么重启电脑, 它依旧都躺在那个文件夹里面, 这些内存就是存储在外存上面的.


外存的访问速度, 相对于内存来说是更慢的, 比如下面是一个参考的各级硬件的执行速度比较

在这里插入图片描述

上面的Read from Memory就是指从内存中读取数据, 而下面Read from disk则是指从硬盘中读取数据. 可以看到, 它们之间的差距, 差不多是 100 倍的差距. 也就是说, 我读取一次硬盘, 就差不多相当于读 100 次内存, 这个差距还是相当恐怖的.

总而言之, 我们目前就只需要知道下面几件事情就行:

  1. 我们的软件是运行在内存上面的, 我们的文件之类的都是存储在硬盘上面的
  2. 硬盘的读取速度, 远远慢于内存
  3. 断电后, 硬盘的数据不会丢失, 内存中的数据会丢失

CPU

CPU, 又被称作是中央处理器. 可以说是当今计算机中最重要的组件, CPU 在一个计算机中的地位就和大脑在人身体中的地位类似, 没有 CPU 电脑就没有办法运行. 那么 CPU 到底是执行了什么工作呢? 它为什么如此重要?

实际上, 我们的操作系统, 包括这些软件, 都是由一条一条的指令组成的, 而这些指令, 如果没有执行者, 那么就是一条条静态的数据罢了, 没有任何的作用.

而 CPU 就是这些指令的执行者, 它负责去疯狂的执行各个软件的指令, 从而让我们的软件能够逐个的运行起来. 那此时可能有人就要问了: 那我电脑上这么多软件, 全都是靠 CPU 一个人来顶, 它顶的住吗?

实际上, 当今的 CPU 的运行速度, 已经到达了一种非常恐怖的程度, 例如我们可以在任务管理器中, 看到我们 CPU 的一些属性

在这里插入图片描述

可以看到, 它这里有一个速度, 以及右边还有一个基准速度, 这个实际上就是去描述 CPU 的执行速度的, 那么它是什么意思呢?

这里我们不进行复杂的介绍, 我们就简单的理解为这个是 CPU 一秒钟执行的指令数即可. 如果严格来说其实并不是这样的, 但是差别不会很大, 如果想要了解这部分知识, 那么可以去了解一下 CPU 的时钟频率和时钟周期.

那么如果我们以简化的方式理解, 那么这个 CPU 这里就是一秒钟可以执行 30 亿条指令, 这个数字是非常恐怖的. 对于去执行那么一些简单的程序来说, 可以说是绰绰有余了.


既然提到了 CPU, 那么就不得不提到另外一个话题, 就是多核 CPU 的诞生. 在过去, 这些 CPU 都是只有单个核心的(核心就可以理解为是一个能够完成一些运算功能的集合体, 里面由很多运算器组成, 完成指令执行, 简易运算操作). 在最开始的时候, 都是通过提高 CPU 内部电路的集成程度, 去提高 CPU 的运行速度. 随着集成程度的提高, 那么此时自然那些运算单元就也需要去变得更小, 但是小到一定程度之后, 那么此时进一步去缩小难度就会比较高了.

那为了能够进一步的提升 CPU 的运行速度, 此时就不再从核心本身下手, 而是从核心的数目下手. 就好比有一个活, 一个人干到极限了, 没办法再让它继续干了, 此时我就多叫几个人.

随后 CPU 的核心数目就逐渐的从单核慢慢变多, 例如什么 4 核, 8 核, 甚至一些高端服务器 CPU 还有 64 核, 128核这样的.

指令

上面提到 CPU 时, 我们提到了一个指令的概念, 那么指令到底是什么东西呢?

指令本质上其实也是一串二进制数字, 但是它能够去告诉 CPU 去执行什么操作, 以及 CPU 执行操作所需要的一些资源, 例如我想要让 CPU 去执行一个加法指令, 那么此时我就会通过指令的前面一部分告诉 CPU, 这是一个加法操作, 然后其他部分就去给 CPU 提供操作数. 这个提供的过程, 既可以是直接提供操作数, 也可以提供地址或者寄存器, 然后让 CPU 去自己读取.

此时可能有人就要问了: 寄存器是什么呢?

实际上, 寄存器是相较于内存来说更加小的一个存储器, 相应的, 它的访问速度也比内存还要更快. 它一般就用于存储一些中间数据这样的, 例如执行一个连续的加法10 + 20 + 30 + 40, 那么 CPU 就可以先去执行10 + 20, 然后把结果30存到寄存器里面, 随后读取下一个数后, 再把它从寄存器里面取出来继续相加.

操作系统

初识操作系统

目前市面上还是有非常多操作系统的, 例如 Windows, Linux, macOS 等等. 它们虽然使用场景不同, 叫法不同, 但是他们的功能都是比较一致的, 就是用于去管理硬件和软件的设备.

对上要去给软件提供稳定的运行环境, 对下要去管理各式各样的硬件设备.

在这里插入图片描述

对于软件来说, 主要就是实现程序之间的隔离. 例如在我们的电脑上, 如果说 QQ 突然崩溃了, 那么此时他不会影响到另外一边正常运行的微信. 不能说我的某一个程序爆炸了后, 就和连锁一样导致我的其他所有程序全部爆炸了, 那这样用户体验自然是很差的.

而对下管理硬件设备, 实际上也是需要通过一系列手段去管理的. 因为市面上有各式各样的硬件, 我们就以鼠标为例, 有一些鼠标, 它可能除了可以左键右键滚轮, 甚至还可以有什么侧键, 调整DPI这样的操作, 那么此时操作系统怎么认识这些额外的操作呢?

我们需要知道的是, 操作系统本质上也是一个管理软件, 它也不可能认识世界上的所有硬件设备, 因此即使一些常见功能它会支持, 但总有遇到不认识的设备的时候. 那么此时就需要靠生产硬件的厂商, 去提供一个名为驱动程序的软件, 告诉操作系统这是什么设备, 可以有什么功能, 从而让操作系统认识它的特定功能, 从而完成对这个硬件设备的控制.

内核态和用户态

我们上面提到了操作系统会提供给软件一个稳定的运行环境, 但是实际上这个运行的环境也是有一些区分的. 其中最核心的一个区别就是内核态和用户态的区别.

内核态顾名思义, 就是操作系统的核心部分, 一些操作系统的核心功能, 就是通过这里去提供的. 例如一些去操作底层硬件设备的功能, 此时操作系统就可以通过提供一些可以操作内核的 API, 使得程序员可以通过 API 去进行一些例如操作硬盘, 网卡这样的功能.

而用户态, 则是用于去运行一些基本应用的, 一般来说, 内核态都需要去给这些运行在用户态的软件提供一系列支持.

进程/任务

进程是什么

进程要解释起来比较抽象, 不过我们可以直接通过一个方式直观了解一下进程. 实际上如果你对任务管理器比较熟悉, 那么其实你应该每天都能看到进程

在这里插入图片描述

进程, 如果不严格的说, 可以说是一个运行起来的程序.

但是实际上我们从上面的这个图片也可以看出, 这个谷歌浏览器一个程序就对应了非常多的进程. 因此严格的来说, 进程和运行中的程序并不是一个一一对应的关系, 而可能是一个多对一的关系.

同时, 这里我们还需要提到程序的这个概念, 程序并不是我们上面看到的这个运行中的程序, 而是那种躺在文件资源管理器里面的那种可执行文件, 如下所示
在这里插入图片描述

程序是一个静态的概念, 与进程不同, 进程是一个动态的概念.


同时, 我们能够在任务管理器中看到, 每一个进程都是消耗了一定的资源的, 例如 CPU, 硬盘, 内存之类的

在这里插入图片描述

实际上, 一个进程如果想要运行, 那么就需要操作系统去分配一定的系统资源去给他运行, 就类似于一个员工, 你得给他发点工资, 他才会给你干活一样.

而这些资源的分配, 都是以每一个进程为单位的. 因此也可以说, 进程是系统资源分配的基本单位.

进程的管理

如果在任务管理器中上下观察一下, 会发现我们的操作系统中, 有一大堆的进程在运行. 那么在我们的操作系统中, 这么多进程在运行, 那么操作系统是如何进行管理的呢?

首先, 操作系统内部有一个结构体来去描述进程的属性, 这个结构体也叫做进程控制块(PCB, Process Control Block), 这个进程控制块就是用来描述进程的属性的, 一个进程可能使用一个 PCB 也可能使用多个 PCB

接下来就是组织进程, 我们的操作系统会使用一种类似于双向链表的数据结构来组织 PCB. 如果新增进程, 那么就会在这个链表中插入一个进程, 如果关闭进程, 那么就会进行对应的删除操作.


那么PCB是如何描述进程的呢? 我们这里就先了解 PCB 中的一部分内容

  1. pid:

pid是进程的身份表示, 每一时刻每一个进程都会有自己独特的pid. 例如我们在任务管理器中就可以看到进程的pid

在这里插入图片描述

在这里插入图片描述

  1. 内存指针:

通过任务管理器我们可以看到, 每一个进程运行的时候, 都需要一定的内存去运行, 而这个内存指针就是用来描述进程占据的内存区域的.

在这里插入图片描述

为了描述一个进程的内存占用, 会用一组内存指针去描述存储专用数据的区域, 存储指令的区域, 存储临时数据的区域等. 简单的说, 内存指针就是用于去描述进程占用的内存资源的.

  1. 文件描述符表:

一个进程如果要涉及文件操作, 实际上就是要去操作硬盘. 这个文件描述符表就是用于描述进程关联了哪些文件, 要操作哪些文件. 简单的说, 文件描述符表就是用来描述进程占用的硬盘资源.

那么此时可能有要问了, 之前我们讲了那么多 CPU 的东西, 那 CPU 的资源占用, 又是如何去描述的呢?

要提到进程占用的CPU资源要如何体现的, 那么这就不得不提到一个新的话题, 进程的调度.

进程的调度

早期的操作系统是一个单任务的操作系统, 单任务操作系统指的就是只允许同一个时刻只能有一个任务运行, 每当我们要换一个应用程序的时候, 上一个应用程序就要退出. 但是很明显现在的操作系统已经不是这样的了, 那么现在的操作系统是如何支持同时运行多个任务的呢?

首先我们要知道, CPU 是有核心的, 并且 CPU 的每一个核心, 都只能在同一个时间段内执行一个进程的指令. 就比如我现在有一个 QQ 和一个微信, 如果 QQ 占用了 CPU 的一个核, 那么微信就没有办法在这个核上面继续跑了

在这里插入图片描述

那么此时可能有人会说: 我明白了, 是因为现在CPU有多核了所以能同时执行多个任务, 只能说对了一部分但是不完全对. 因为我们现在的家用计算机, 都是差不多 10 个核心左右的. 而我们通过任务管理器可以看到, 我们的电脑上绝对是不止这么一点进程的.

在这里插入图片描述

那么就说明, 似乎一个 CPU 核心, 也是应该可以去同时运行多个进程的. 其实在早期的CPU还是单核的时候, 也有操作系统支持了多进程同时执行的效果, 那么是如何实现的呢?

实际上靠的就是进程的调度来实现的, 我们的各个程序有多条指令, 它们会以非常快的速度在CPU上轮番执行指令, 这种进程将 CPU 的核心分时复用的做法就能被称为是进程的调度. 例如我们上面举的 QQ 和微信的例子, 他们就会一直在 CPU 的一个核心上面轮转.

在这里插入图片描述

同时由于 CPU 的调度速度极快, 一秒能够执行上亿条指令, 因此我们人体是感觉不到这种调度的, 在我们的感觉上就类似于同时执行一样, 那么这种微观上非同时但宏观上看起来是同时发生的情况就被称为并发. 同时, 由于现代的 CPU 中一般会有多个核心, 因此有时候也会出现真正的同时执行的情况, 这种微观上也是同时发生的情况就被称为并行.

不过一般来说, 我们从书写程序的角度去看, 两者并没有什么很大的区别, 因此我们一般来说会将两者共同称作是并发.


那么要实现进程的调度, 自然也需要一些东西去描述相关的属性, 不能说想怎么调度就怎么调度, 因此 PCB 中也有一些相关的属性, 下面我们简单了解一下和进程调度有关的几个 PCB 属性

  1. 进程的状态:
  • 就绪状态: 运行中或者随时准备可以马上执行
  • 阻塞状态: 某个条件不具备, 导致这个进程暂时无法参与CPU的调度执行

这里的状态并不是所有的状态, 我们这里只是简单了解一下

  1. 进程的优先级:

进程的优先级会去影响 CPU 优先调度哪个进程, 例如操作系统自带的一些进程, 优先级肯定更高. 能够更好的调配系统资源

  1. 进程的上下文:

假如说有一个进程, 它从 CPU 上下来了, 那么此时它运行到哪了, 就需要去记录下来. 后续继续调度到这个进程的时候, 那么就可以从这个记录的位置继续执行, 而不是从头开始.

就类似于一些单机游戏的存档读档, 下线前保存一下, 下次上来玩直接从存好的位置继续即可, 而不是从头开始重新玩.

  1. 进程的记账信息:

记录每一个进程 CPU 资源的占用情况, 然后操作系统会根据这个信息适当调整进程的调度情况. 这个主要是为了防止进程饿死, 简单地说就是防止由于优先级导致的一个进程一直吃不到 CPU 的调度, 一直在阻塞.

虚拟内存地址

试想, 假如我们的程序能够直接去操作内存, 那么假如发生了内存越界, 此时就可能会出现问题. 如图所示

在这里插入图片描述

此时进程 1, 可能就会因为其他进程把自己的数据修改了, 随后直接报错崩溃. 这很明显是不正常的, 因此操作系统中就引入了虚拟内存地址的机制.

操作系统在分配内存给进程的时候, 都只会分配虚拟的内存. 同时将这个虚拟的地址与对应的物理地址建立一定的映射关系, 随后进程在操作的时候就会经过操作系统检查, 看看有没有在虚拟内存中越界. 确认无误后再映射到物理内存中, 从而防止某进程越界访问内存导致其他进程被影响
在这里插入图片描述

进程间的通信

什么是进程间的通信, 实际上就是指两个进程之间要产生交互. 但是由于要给进程提供一个稳定的运行环境, 操作系统又对进程进行了隔离. 那么此时如何实现进程间的通信呢?

此时我们就可以给进程去准备一些公共空间, 然后让它们在这个公共空间里面去进行交互, 就好比张三和李四想要一起玩, 但是他们的父母管的都比较严厉, 不准在家里面玩, 此时它们就可以自己约的出去外面玩.

常见的交互方式就是去通过文件或者是网络的方式去实现进程的通信, 其实实现进程间的通信并不只有这两个手段, 不过我们这里就不细致介绍了.

线程

线程的出现

引入多个进程的初心, 就是为了实现并发编程. 虽然多进程实现并发编程效果也非常理想, 但是世界上没有十全十美的东西, 进程实现并发编程也有它的缺点, 缺点就是效率不高

进程的各种操作, 例如创建, 销毁和调度, 执行效率都是比较慢的, 为什么?上面也说过进程是系统资源分配的基本单位, 进程的各个操作可能都会涉及到申请系统资源, 申请资源时就离不开分配内存, 而分配内存又是一个耗时较长的操作

那么为了解决上面这个进程申请系统资源的开销过大的问题, 就引入了一个新的概念, 线程

线程是什么

线程, 也被称作轻量级进程, 线程的所有操作都比进程的各个操作都要更快更轻量, 但是线程需要依靠进程才可以存在. 一个进程可以有一个线程, 也可以有多个线程. 一个进程在最开始的时候至少会有一个线程, 线程负责执行代码工作. 并且我们也可以根据需求创建出更多的线程, 从而实现并发编程的效果.

我们前面所谈到的调度, 都是说的调度进程, 但是实际上由于我们前面讨论的情况都是对应的一个进程只有一个线程, 因此才说是调度进程. 但是实际上调度的应该是进程的里的那个线程, 并且有一些进程是可以有多个线程的, 此时我们也是可以独立的对各个线程进行调度的.

为了能够实现线程的调度, 线程也是有 PCB 来描述的. 这也就是为什么一个进程可能有多个 PCB, 因为它有多个线程需要多个 PCB 去描述线程. 但是与进程不同的是, 线程的 pid, 内存指针, 文件描述符表都是共用的.

这也就是为什么线程的各个操作都会更加的轻量的原因, 因为相比于进程来说, 创建一个线程直接复用之前创建进程申请的系统资源即可, 省去了申请资源的开销.

那么最终我们就可以得出一个结论: 线程是调度执行的基本单位

线程可能出现的问题

操作系统为了防止进程之间产生影响, 对进程进行了隔离. 但是我们的线程本身就是存在于同一个进程里面的, 那么此时是否可能会引发一些问题呢? 当然可能, 接下来我们就来通过一个例子来了解一下, 线程可能会出现的问题.

假如现在一张桌子上面有 100 个苹果, 需要张三同学去把这 100 个苹果给吃了. 那么很明显, 此时如果它一个人去吃, 还是比较难的, 那么此时他就可以去将这 100 个苹果分到两个桌子上面, 然后再叫个人去吃另外一个桌子的苹果

在这里插入图片描述

此时的这个方式就有一点类似于多进程的方式, 很明显这样子做, 虽然加快了速度, 但是也消耗了一块额外空间. 因此张三改变策略, 直接让这个人到自己的桌子上面来一起吃

在这里插入图片描述

此时就相当于是多线程的方式了, 很明显此时即使没有消耗额外的空间, 效率也提高了. 张三一看, 这个方法行, 因此叫了更多的人来一起帮忙吃

在这里插入图片描述

但是此时人数一多, 反而又变的有一点难受了, 只有围在桌子边上的人才能直接吃到, 而在外围的人则需要去挤到桌子边上去才能吃到苹果. 此时实际上就相当于是, 线程过多, 调度的开销反而更大, 效率没有提高反而降低了.

同时, 人数一多也会引发一些其他问题, 假如有一个苹果同时被两个人给拿了. 那么此时这个苹果给谁呢?

在这里插入图片描述

此时这种两个线程在同一个资源上面产生了冲突的情况, 也被称作是线程安全问题. 这个是线程中要研究的重点问题之一.

同时, 人多起来后, 假如说有一个人一直吃不到苹果, 那么此时它就有点暴躁了, 可能就想要直接掀桌了.

在这里插入图片描述

而这个情况, 就可以看作是线程的异常, 如果线程的异常没有得到妥善的处理, 那么此时整个进程都有可能直接崩溃. 什么是妥善的处理, 例如上面在那个人暴怒前, 有人就发现了异常, 直接跟他说: 大哥消消气, 来这个位置给你, 你上去吃吧. 那么此时就不会产生问题了.

在这里插入图片描述

如果是体现在代码上面, 那就是我们去把这个异常给捕获掉, 然后根据对应的异常情况, 进行对应的处理.

线程与进程的联系和区别

上面我们提到了进程和线程两个概念, 我们这里就简单总结一下这两个概念之间的联系以及区别

  1. 进程包含了线程, 一个进程里面可以只有一个线程, 也可以有多个线程
  2. 创建进程的开销大, 创建线程的开销小
  3. 进程可以独立存在, 线程必须依赖进程存在
  4. 每个进程独享一个 PCB, 而多个线程可能会共用 PCB 中的一些属性
  5. 进程是资源分配的基本单位, 线程是调度执行的基本单位

协程

线程虽然轻量, 但是终究还是要经过 CPU 的调度, 因此还有一种更加轻量的设定, 就是协程. 它也被称作是轻量级线程, 它的调度不用经过内核态, 而是可以直接在用户态去进行调度, 因此相对于线程来说, 它又是更加轻量的一种选择

不过由于 Java 中并没有官方指定的协程, 因此这里我们就不过多介绍了, 想要进一步了解可以了解一下 Java 中的虚拟线程这个概念

初识Thread类

Thread类是什么

线程, 归根究底是操作系统中的一个概念, 那么如果我们想要操作线程, 那么就需要通过操作系统提供的一些 API 去进行操作. 而对应不同操作系统, 提供的 API 也是不相同的, 因此 Java 为了满足它的跨平台特性, 就针对操作系统提供的 API 进行了封装, 把它们变为了同一套东西.

而 Thread 类, 就是 Java 基于操作系统去封装的用于操作线程的 API. 我们只需要通过这个类, 我们就可以去控制操作系统中的线程了

Thread初步使用

我们依旧是介绍一个东西的老套路, 先写一个 Hello World 程序来试试水. 我们暂时没有必要去理解其中每一步的具体含义, 我们这里主要目的是写出第一个程序.

首先我们创建一个类, 去继承 Thread 类

class MyThread extends Thread {
    
}

然后去重写里面的run()方法, 打印一个 Hello World. 这个run()方法的核心功能就是去告诉线程, 它的工作是什么. 这里它的工作就是去打印一个 Hello World

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello World");
    }
}

最后, 我们就在main()方法中去创建这个 MyThread 类的对象, 然后调用里面的start()方法即可. start()方法用于去启动线程

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

最后运行查看打印结果

在这里插入图片描述

此时我们就完成了我们多线程的第一个程序, 此时可能有人觉得这个似乎和我们之前学习的东西没有什么区别. 确实我们现在的这个简单程序, 是看不出具体的效果的, 实际上它的执行流程应该是如图所示的

在这里插入图片描述

这里我们启动 main 方法后, 首先会自动启动一个 主线程, 然后主线程再去调用这个start()方法启动 MyThread 线程, 随后 MyThread 线程再去打印这个 Hello World.

现在即使觉得有一点抽象, 难以理解也是正常的, 后续在进行深入的学习的时候, 就可以逐渐的理解了. 我们这里只是初步的了解Thread类, 并且进行第一个程序的书写, 其他更加细节的内容我们就在后续再进行介绍了.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VSCode引用Eigen库无法识别问题解决

VSCode引用Eigen库无法识别问题解决 问题解决 问题 在Ubuntu下使用vscode开发C/C项目时引用了Eigen库,出现Eigen::Vector3d无法识别的问题,提示"no definition found for Vector3d"。但是程序可以正常编译通过。 解决 将 intelli Sense Engi…

【学习资料】袋中共36个球,红白黑格12个,问能一次抽到3个红4个白5个黑的概率是多少?

1、公式计算 1.1 题目1 袋中共 36 36 36个球, 红 \fcolorbox{red}{#FADADE}{\color{red}{红}} 红​ 白 \fcolorbox{white}{#808080}{\color{white}{白}} 白​ 黑 \fcolorbox{#808080}{#0D0D0D}{\color{#808080}{黑}} 黑​各 12 12 12个,问能一次抽到 3…

事件循环异步代码输出顺序题目【基础考核】

简单的事件循环,一道异步代码执行输出顺序问题? 第一题 setTimeout(() > {console.log("A")Promise.resolve().then(() > { console.log("B"); });}, 1000);Promise.resolve().then(() > { console.log("c"); });new Prom…

JSON.parseArray 内存溢出

实际上我的JSON如下: 如果用以下代码:JVM的内存直接飙到内存溢出,报错OutOfMemoryError: Java heap space Object oo JSON.parseArray(json, TestVo.class) 如果我换成了这样,就没事: Object oo JSON.parseObject(…

1.2 测试基础

欢迎大家订阅【软件测试】 专栏,开启你的软件测试学习之旅! 文章目录 前言1 测试分类1.1 按生产阶段划分1.2 按代码可见度划分1.3 其他测试 2 质量模型 前言 在软件开发过程中,测试是确保产品质量的重要环节。本文详细讲解了软件测试分类以及…

Python Email库:发送与接收邮件完整指南!

Python Email库如何集成?怎么优化Python Email库性能? Python作为一种强大的编程语言,提供了丰富的库来处理电子邮件,其中最著名的就是Python Email库。AokSend将深入探讨如何使用Python Email库来发送和接收邮件,帮助…

SpringCloud config native 配置

SpringCloud config native 配置 1.概述 最近项目使用springCloud 框架,使用config搭建git作为配置中心。 在私有化部署中,出现很多比较麻烦的和鸡肋的设计。 每次部署都需要安装gitlab 有些环境安装完gitlab,外面不能访问,不给开…

适合运动的骨传导耳机哪款好?分享五款性能卓越骨传导耳机

面对琳琅满目的骨传导耳机市场,是不是既兴奋又迷茫?别怕,我来给你支几招!选耳机,最重要的是适合自己,别被各种噱头和价格差异绕晕了头。价格高低与品质好坏并非绝对正比,关键看性价比和个人需求…

Google SERP API 对接说明

Google SERP API 对接说明 Google SERP(Search Engine Results Page)是用户在Google搜索引擎中输入查询后看到的结果页面。它显示自然搜索结果、广告、特色摘要、知识图谱以及图片、视频等多种内容,旨在为用户提供最相关的信息。 本文将详细…

物联网开发+充电桩管理系统+充电桩系统源码

简述 SpringBoot 框架,充电桩平台充电桩系统充电平台充电桩互联互通协议云快充协议1.5新能源汽车电动自行车公交车-四轮车充电充电源代码充电平台源码Java源码无加密项目 介绍 云快充协议云快充1.5协议云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充…

鸿蒙应用生态构建的核心目标

保护开发者和用户利益的同时维护整体系统的安全性,对生态构建者是至关重要的。以开发者为中心,构建端到端应用安全能力,保护应用自身安全、运行时安全,保障开发者权益,是鸿蒙应用生态构建的核心目标。 应用生命周期主要…

大数据-137 - ClickHouse 集群 表引擎详解2 - MergeTree 存储结构 一级索引 跳数索引

点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…

CAD图1

文章目录 选择直线工具选择圆形选中圆形 选择直线工具 画一条十字中心线 选择圆形 以十字中心为起点画一个半径为 53 的圆形 选中圆形 选中圆形,捕捉右侧圆形焦点

【北京迅为】《STM32MP157开发板使用手册》- 第四十章 二值信号量实验

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐…

②MODBUS TCP 转 RS485(RS485与TCP数据双向互传)MODBUS TCP与MODBUS RTU互转(无需编程 独立通道)

型号:1路总线TCP网关(单网口) MS-A1-5011 1路总线TCP网关(双网口) MS-A2-5011 2路总线TCP网关(单网口) MS-A1-5021 2路总线TCP网关(双网口) MS-A2-5021 4路总…

怎样把PPT上顽固的图标删了

例如: 解决: 首先打开下载好的PPT模板,然后在视图选项卡里面找到幻灯片母版。 进入幻灯片母版后,找到第一页母版页就会看到LOGO了,这时使用鼠标就可以选中删除啦。

【Web】从网安的角度浅聊Groovy命令执行

什么是 Groovy? Groovy 是一种基于 Java 平台的动态语言,旨在提高开发效率。它与 Java 语言高度兼容,允许开发者以更简洁的方式编写代码。Groovy 支持面向对象编程、闭包、DSL(领域特定语言)等特性,使得它…

OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(上)

往期知识点记录: 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~ OpenHarmony(鸿蒙南向开发)——轻量系统STM32F407芯片移植案…

Eclipse离线安装Tomcat插件

Eclipse离线安装Tomcat插件 最近的自己在对低版本的代码的进行维护补丁,不得不采用Eclipse 来进行跑项目,真的是折磨 其中遇到一个问题就是打开Eclipse的2021版,安装Tomcat的插件,发现好家伙,就是死活在线安装失败 (喵的,真的是让我抓耳挠腮!!哈哈哈) 无奈,只好采用离线安装,特…

C# 携手 7-Zip 命令行:大文件压缩的终极武器?

前言 嗨,大家好! 今天咱们来聊聊如何用 C# 调用 7-Zip 命令行来压缩大文件,这是个既高效又稳定的好办法,亲测有效! 在实际工作中,压缩文件几乎是家常便饭,但可惜的是,许多常用的方…