线程安全性的原理分析学习

初步认识Volatile

一段代码引发的思考 下面这段代码,演示了一个使用volatile以及没使用volatile这个关键字,对于变量更新的影响 
package com.sp.demo;/*** @author : lssffy* @Description :* @date : 2024/2/16 18:42*/
public class VolatileDemo {public /*volatile*/ static boolean stop=false;public static void main(String[] args) throwsInterruptedException {Thread thread = new Thread(() -> {int i = 0;while (!stop) {i++;}});thread.start();System.out.println("begin start thread");Thread.sleep(1000);stop = true;}
}

volatile 的作用

volatile 可以使得在多处理器环境下保证了共享变量的可见性,那么到底什么是可见性呢?不知道大家有没有思考过这个问题 
在单线程的环境下,如果向一个变量先写入一个值,然后在没有写干涉的情况下读取这个变量的值,那这个时候读取到的这个变量的值应该是之前写入的那个值。这本来是一个很正常的事情。但是在多线程环境下,读和写发生在不同的线程中的时候,可能会出现:读线程不能及时的读取到其他线程写入的最新的值。这就是所谓的可见性 
为了实现跨线程写入的内存可见性,必须使用到一些机制来实现。而volatile就是这样一种机制 volatile 关键字是如何保证可见性的? 我们可以使用【hsdis】这个工具,来查看前面演示的这段代码的汇编指令,具体的使用请查看使用说明文档 在运行的代码中,设置jvm参数如下 【-server -Xcomp -XX:+UnlockDiagnosticVMOptions XX:+PrintAssembly XX:CompileCommand=compileonly,*App.*(替换成实际运行的代码)】 然后在输出的结果中,查找下lock指令,会发现,在修改带有volatile 修饰的成员变量时,会多一个lock指令。lock是一种控制指令,在多处理器环境下,lock 汇编指令可以基于总线锁或者缓存锁的机制来达到可见性的一个效果。 为了让大家更好的理解可见性的本质,我们需要从硬件层面进行梳理 。

从硬件层面了解可见性的本质

一台计算机中最核心的组件是CPU、内存、以及I/O设备。在整个计算机的发展历程中,除了CPU、内存以及I/O设备不断迭代升级来提升计算机处理性能之外,还有一个非常核心的矛盾点,就是这三者在处理速度的差异。CPU的计算速度是非常快的,内存次之、最后是IO设备比如磁盘。而在绝大部分的程序中,一定会存在内存访问,有些可能还会存在I/O设备的访问 为了提升计算性能,CPU从单核升级到了多核甚至用到了超线程技术最大化提高 CPU 的处理性能,但是仅仅提升CPU性能还不够,如果后面两者的处理性能没有跟上,意味着整体的计算效率取决于最慢的设备。为了平衡三者的速度差异,最大化的利用CPU提升性能,从硬件、操作系统、编译器等方面都做出了很多的优化
1. CPU增加了高速缓存 
2. 操作系统增加了进程、线程。通过CPU的时间片切换最大化的提升CPU的使用率 
3. 编译器的指令优化,更合理的去利用好CPU的高速缓存 然后每一种优化,都会带来相应的问题,而这些问题也是导致线程安全性问题的根源。为了了解前面提到的可见性问题的本质,我们有必要去了解这些优化的过程

CPU 高速缓存

线程是CPU调度的最小单元,线程设计的目的最终仍然是更充分的利用计算机处理的效能,但是绝大部分的运算任务不能只依靠处理器“计算”就能完成,处理器还需要与内存交互,比如读取运算数据、存储运算结果,这个I/O操作是很难消除的。而由于计算机的存储设备与处理器的运算速度差距非常大,所以现代计算机系统都会增加一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存和处理器之间的缓冲:将运算需要使用的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步到内存之中。

在这里插入图片描述
通过高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来了更高的复杂度,因为它引入了一个新的问题,缓存一致性。
*

缓存一致性

首先,有了高速缓存的存在以后,每个CPU的处理过程是,先将计算需要用到的数据缓存在CPU高速缓存中,在CPU进行计算时,直接从高速缓存中读取数据并且在计算完成之后写入到缓存中。在整个运算过程完成后,再把缓存中的数据同步到主内存。 
由于在多CPU种,每个线程可能会运行在不同的CPU内,并且每个线程拥有自己的高速缓存。同一份数据可能会被缓存到多个CPU中,如果在不同CPU中运行的不同线程看到同一份内存的缓存值不一样就会存在缓存不一致的问题  为了解决缓存不一致的问题,在CPU层面做了很多事情,主要提供了两种解决办法 
1. 总线锁 
2. 缓存锁 
总线锁和缓存锁 
总线锁,简单来说就是,在多cpu下,当其中一个处理器要对共享内存进行操作的时候,在总线上发出一个LOCK#信号,这个信号使得其他处理器无法通过总线来访问到共享内存中的数据,总线锁定把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,这种机制显然是不合适的  
如何优化呢?最好的方法就是控制锁的保护粒度,我们只需要保证对于被多个 CPU 缓存的同一份数据是一致的就行。所以引入了缓存锁,它核心机制是基于缓存一致性协议来实现的。 
缓存一致性协议 
为了达到数据访问的一致,需要各个处理器在访问缓存时遵循一些协议,在读写时根据协议来操作,常见的协议有MSI,MESI,MOSI等。最常见的就是MESI协议。接下来给大家简单讲解一下MESI MESI 表示缓存行的四种状态,分别是 
1. M(Modify) 表示共享数据只缓存在当前 CPU 缓存中,并且是被修改状态,也就是缓存的数据和主内存中的数据不一致 
2. E(Exclusive) 表示缓存的独占状态,数据只缓存在当前CPU缓存中,并且没有被修改 
3. S(Shared) 表示数据可能被多个CPU缓存,并且各个缓存中的数据和主内存数据一致 
4. I(Invalid) 表示缓存已经失效 在 MESI 协议中,每个缓存的缓存控制器不仅知道自己的读写操作,而且也监听(snoop)其它Cache的读写操作

在这里插入图片描述
在这里插入图片描述

对于MESI 协议,从 CPU 读写角度来说会遵循以下原则: CPU读请求:缓存处于M、E、S状态都可以被读取,I状态CPU只能从主存中读取数据 
CPU写请求:缓存处于M、E状态才可以被写。对于S状态的写,需要将其他CPU中缓存行置为无效才可写 使用总线锁和缓存锁机制之后,CPU对于内存的操作大概可以抽象成下面这样的结构。从而达到缓存一致性效果。

在这里插入图片描述

总结可见性的本质

由于CPU高速缓存的出现使得 如果多个cpu同时缓存了相同的共享数据时,可能存在可见性问题。也就是CPU0修改了自己本地缓存的值对于 CPU1 不可见。不可见导致的后果是 CPU1 后续在对该数据进行写入操作时,是使用的脏数据。使得数据最终的结果不可预测。 希望想在代码里面去模拟一下可见性的问题,实际上,这种情况很难模拟。因为我们无法让某个线程指定某个特定CPU,这是系统底层的算法, JVM 应该也是没法控制的。还有最重要的一点,就是你无法预测CPU缓存什么时候会把值传给主存,可能这个时间间隔非常短,短到你无法观察到。最后就是线程的执行的顺序问题,因为多线程你无法控制哪个线程的某句代码会在另一个线程的某句代码后面马上执行。 
所以我们只能基于它的原理去了解这样一个存在的客观事实 。了解到这里,大家应该会有一个疑问,刚刚不是说基于缓存一致性协议或者总线锁能够达到缓存一致性的要求吗?为什么还需要加 volatile 关键字?或者说为什么还会存在可见性问题呢? 

MESI 优化带来的可见性问题

MESI 协议虽然可以实现缓存的一致性,但是也会存在一些问题。 就是各个CPU缓存行的状态是通过消息传递来进行的。如果CPU0 要对一个在缓存中共享的变量进行写入,首先需要发送一个失效的消息给到其他缓存了该数据的CPU。并且要等到他们的确认回执。CPU0 在这段时间内都会处于阻塞状态。为了避免阻塞带来的资源浪费。在cpu中引入了Store Bufferes。 

在这里插入图片描述
CPU0只需要在写入共享数据时,直接把数据写入到store bufferes 中,同时发送invalidate 消息,然后继续去处理其他指令。
当收到其他所有CPU发送了invalidate acknowledge消息时,再将 store bufferes 中的数据数据存储至 cache line中。最后再从缓存行同步到主内存。
在这里插入图片描述
但是这种优化存在两个问题
1. 数据什么时候提交是不确定的,因为需要等待其他 cpu给回复才会进行数据同步。这里其实是一个异步操作
2. 引入了storebufferes 后,处理器会先尝试从storebuffer中读取值,如果 storebuffer 中有数据,则直接从storebuffer 中读取,否则就再从缓存行中读取 。
在这里插入图片描述
exeToCPU0和exeToCPU1分别在两个独立的CPU上执行。假如CPU0 的缓存行中缓存了isFinish 这个共享变量,并且状态为(E)、而Value可能是(S)状态。 那么这个时候,CPU0在执行的时候,会先把value=10的指令写入到storebuffer中。并且通知给其他缓存了该value变量的CPU。在等待其他CPU通知结果的时候,CPU0会继续执行isFinish=true 这个指令。 而因为当前CPU0缓存了isFinish并且是Exclusive状态,所以可以直接修改isFinish=true。这个时候CPU1发起read操作去读取isFinish的值可能为true,但是value的值不等于10。
这种情况我们可以认为是CPU的乱序执行,也可以认为是一种重排序,而这种重排序会带来可见性的问题 这下硬件工程师也抓狂了,我们也能理解,从硬件层面很难去知道软件层面上的这种前后依赖关系,所以没有办法通过某种手段自动去解决。 所以硬件工程师就说: 既然怎么优化都不符合你的要求,要不你来写吧。
所以在 CPU 层面提供了 memory barrier(内存屏障)的指令,从硬件层面来看这个 memroy barrier就是CPU flush store bufferes 中的指令。软件层面可以决定在适当的地方来插入内存屏障。

CPU 层面的内存屏障

什么是内存屏障?从前面的内容基本能有一个初步的猜想,内存屏障就是将 store bufferes 中的指令写入到内存,从而使得其他访问同一共享内存的线程的可见性。 X86的memory barrier指令包括lfence(读屏障) sfence(写屏障) mfence(全屏障)Store Memory Barrier(写屏障) 告诉处理器在写屏障之前的所有已经存储在存储缓存(store bufferes)中的数据同步到主内存,简单来说就是使得写屏障之前的指令的结果对屏障之后的读或者写是可见的 
Load Memory Barrier(读屏障) 处理器在读屏障之后的读操作,都在读屏障之后执行。配合写屏障,使得写屏障之前的内存更新对于读屏障之后的读操作是可见的 Full Memory Barrier(全屏障) 确保屏障前的内存读写操作的结果提交到内存之后,再执行屏障后的读写操作 有了内存屏障以后,对于上面这个例子,我们可以这么来改,从而避免出现可见性问题。

在这里插入图片描述
总的来说,内存屏障的作用可以通过防止CPU对内存的乱序访问来保证共享数据在多线程并行执行下的可见性 但是这个屏障怎么来加呢?回到最开始我们讲 volatile 关键字的代码,这个关键字会生成一个Lock的汇编指令,这个指令其实就相当于实现了一种内存屏障这个时候问题又来了,内存屏障、重排序这些东西好像是和平台以及硬件架构有关系的。作为Java语言的特性,一次编写多处运行。我们不应该考虑平台相关的问题,并且这些所谓的内存屏障也不应该让程序员来关心。

JMM

什么是JMM 
JMM全称是Java Memory Model.  什么是JMM呢? 
通过前面的分析发现,导致可见性问题的根本原因是缓存以及重排序。 而JMM实际上就是提供了合理的禁用缓存以及禁止重排序的方法。所以它最核心的价值在于解决可见性和有序性。 
JMM属于语言级别的抽象内存模型,可以简单理解为对硬件模型的抽象,它定义了共享内存中多线程程序读写操作的行为规范:在虚拟机中把共享变量存储到内存以及从内存中取出共享变量的底层实现细节 通过这些规则来规范对内存的读写操作从而保证指令的正确性,它解决了CPU多级缓存、处理器优化、指令重排序导致的内存访问问题,保证了并发场景下的可见性。需要注意的是,JMM并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序,也就是说在JMM中,也会存在缓存一致性问题和指令重排序问题。只是JMM把底层的问题抽象到JVM层面,再基于CPU层面提供的内存屏障指令,以及限制编译器的重排序来解决并发问题 
JMM抽象模型分为主内存、工作内存;主内存是所有线程共享的,一般是实例对象、静态字段、数组对象等存储在堆内存中的变量。工作内存是每个线程独占的,线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量,线程之间的共享变量值的传递都是基于主内存来完成。
Java 内存模型底层实现可以简单的认为:通过内存屏障(memory barrier)禁止重排序,即时编译器根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令。对于编译器而言,内存屏障将限制它所能做的重排序优化。而对于处理器而言,内存屏障将会导致缓存的刷新操作。比如,对于volatile,编译器将在volatile 字段的读写操作前后各插入一些内存屏障。 
JMM是如何解决可见性有序性问题的 
简单来说,JMM提供了一些禁用缓存以及进制重排序的方法,来解决可见性和有序性问题。这些方法大家都很熟悉:volatile、synchronized、final; 以及 JMM如何解决顺序一致性问题 

重排序问题

为了提高程序的执行性能,编译器和处理器都会对指令做重排序,其中处理器的重排序在前面已经分析过了。所谓的重排序其实就是指执行的指令顺序。 编译器的重排序指的是程序编写的指令在编译之后,指令可能会产生重排序来优化程序的执行性能。 从源代码到最终执行的指令,可能会经过三种重排序。

在这里插入图片描述
2 和3属于处理器重排序。这些重排序可能会导致可见性问题。
编译器的重排序,JMM提供了禁止特定类型的编译器重排序。
处理器重排序,JMM会要求编译器生成指令时,会插入内存屏障来禁止处理器重排序
当然并不是所有的程序都会出现重排序问题 编译器的重排序和CPU的重排序的原则一样,会遵守数据依赖性原则,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序,比如下面的代码,
a=1; b=a;
a=1;a=2;
a=b;b=1;
这三种情况在单线程里面如果改变代码的执行顺序,都会导致结果不一致,所以重排序不会对这类的指令做优化。这种规则也成为 as-if-serial。不管怎么重排序,对于单个线程来说执行结果不能改变。比如
int a=2;
int b=3;
int rs=a*b;
//1
//2
//3
1 和3、2和3存在数据依赖,所以在最终执行的指令中,3 不能重排序到1和2之前,否则程序会报错。由于1和2不存在数据依赖,所以可以重新排列1和2的顺序。

JMM层面的内存屏障

为了保证内存可见性,Java编译器在生成指令序列的适当位置会插入内存屏障来禁止特定类型的处理器的重排序,在JMM中把内存屏障分为四类 

在这里插入图片描述

HappenBefore
它的意思表示的是前一个操作的结果对于后续操作是可见的,所以它是一种表达多个线程之间对于内存的可见性。

所以我们可以认为在JMM中,如果一个操作执行的结果需要对另一个操作课件,那么这两个操作必须要存在happens-before 关系。这两个操作可以是同一个线程,也可以是不同的线程
JMM中有哪些方法建立happen-before规则
程序顺序规则
1. 一个线程中的每个操作,happens-before于该线程中的任意后续操作; 可以简单认为是as-if-serial。 单个线程中的代码顺序不管怎么变,对于结果来说是不变的 顺序规则表示 1 happenns-before 2; 3 happens before 4
在这里插入图片描述
2. volatile 变量规则,对于volatile 修饰的变量的写的操作,一定happen-before 后续对于volatile 变量的读操作; 根据volatile 规则,2 happens before 3
在这里插入图片描述
3. 传递性规则,如果 1 happens-before 2; 3happens before 4; 那么传递性规则表示: 1 happens-before 4;
在这里插入图片描述
4. start 规则,如果线程A执行操作ThreadB.start(),那么线程A的ThreadB.start()操作 happens-before 线程 B 中的任意操作
在这里插入图片描述
5. join规则,如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
在这里插入图片描述在这里插入图片描述

6. 监视器锁的规则,对一个锁的解锁,happens-before于随后对这个锁的加锁

在这里插入图片描述
假设 x 的初始值是 10,线程 A 执行完代码块后 x 的值会变成 12(执行完自动释放锁),线程 B 进入代码块时,能够看到线程 A 对 x 的写操作,也就是线程 B 能够看到 x==12。

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

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

相关文章

5G——小区搜索流程

小区搜索流程 小区搜索目标:读取到SIB1. 小区搜索流程概述:SIB1在PDSCH信道承载,承载SIB1的信道在哪个位置由PDCCH告诉,而PDCCH的基本信息由MIB告诉,MIB信息由广播信道PBCH广播出去,物理信道解调需要解调…

【机构vip教程】Charles(1):Charles的介绍及安装

Charles Charles 是在 Mac (Charles是跨平台的 )下常用的网络封包截取工具,在做移动开发、测试时,我们为了调试与服务器端的网络通讯协议,常常需要截取网络封包来分析。Charles是一个HTTP代理服务器,HTTP监视器,反转代…

算法学习系列(三十五):贪心(杂)

目录 引言一、合并果子(Huffman树)二、排队打水(排序不等式)三、货仓选址(绝对值不等式)四、耍杂技的牛(推公式) 引言 上一篇文章也说过了这个贪心问题没有一个规范的套路和模板&am…

【Spring底层原理高级进阶】轻松掌握 Spring MVC 的拦截器机制:深入理解 HandlerInterceptor 接口和其实现类的用法

🎉🎉欢迎光临🎉🎉 🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀 🌟特别推荐给大家我的最新专栏《Spring 狂野之旅:底层原理高级进阶》 &#x1f680…

OpenAI 发布文生视频大模型 Sora,AI 视频要变天了,视频创作重新洗牌!AGI 还远吗?

一、一觉醒来,AI 视频已变天 早上一觉醒来,群里和朋友圈又被刷屏了。 今年开年 AI 界最大的震撼事件:OpenAI 发布了他们的文生视频大模型 Sora。 OpenAI 文生视频大模型 Sora 的横空出世,预示着 AI 视频要变天了,视…

【机构vip教程】Selenium(2):selenium IDE工具

Selenium IDE工具: 该工具是一个用于构建脚本的初级工具,其实是FireFox的一个插件,拥有一个易于使用的界面。它拥有记录功能,能够记录用户执行的操作,并可以导出为可重复使用的脚本。如果没有编程经验,也可…

MySQL持久化不为人知的一面⭐️卡顿现象的根源与对策

MySQL持久化不为人知的一面⭐️卡顿现象的根源与对策 2024新年新气象,小菜同学又踏上了求职之路,但求职路艰辛,新年第一次面试又被面试官给问住了 面试官:你有没有遇到过因为持久化,把线程的查询、修改请求卡住的情况…

2024.2.18

使用fgets统计给定文件的行数 #include<stdio.h> #include<string.h> int main(int argc, const char *argv[]) {FILE *fpNULL;if((fpfopen("./test.txt","w"))NULL){perror("open err");return -1;}fputc(h,fp);fputc(\n,fp);fput…

算法练习-赎金信(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;哈希表 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。且所在课程未提供测试平台&#xff0c;故实现代码主要为自行测试的那种&#xff0c;以下内容均为个人笔记&#xff0c;旨…

数智化转型|两大电网、五大发电2024年重点工作路线图!

2024年1月&#xff0c;两大电网、五大发电集团陆续召开2024年工作会议&#xff0c;明确了2024年工作目标及重点路线图。从工作会议内容来看&#xff0c;不难发现&#xff0c;加快推动数智化转型&#xff0c;建设新型电力系统成为几大集团未来共同发展目标。会议内容如下&#x…

大数据,对于生活的改变

谷歌通过对于疾病的查询量可以预测一个个h1n1病毒的大爆发&#xff0c; 大数据时代对于人的考验 用户的搜索记录就是一种信息&#xff0c;这种信息会满足其基础相关的词条与其有关的词条&#xff08;最为原始的搜索机制&#xff0c;国内的搜索引擎都是采用这种基础原理。&…

从源代码安装 rocSOLVER 并 调试 rocSOLVER 在 Ubuntu 22.04 平台

0, 下载并编译 rocBLAS 的调试版本 sudo apt install python3.10-venv sudo apt install libmsgpack-dev sudo pip install joblibgit clone --recursive https://github.com/ROCm/rocBLAS.git $ cd rocBLAS/ $ ./install.sh -i -g构建时间也不短 1&#xff0c;下载并编译 roc…

【数据结构与算法】手搓JDK底层ArrayList底层 - 动态数组

数组 在介绍数组之前&#xff0c;我们先来看一段chatGPT给出的对于数组描述&#xff1a; 数组&#xff08;Array&#xff09;是一种线性数据结构&#xff0c;它由一组连续的内存空间组成&#xff0c;用来存储相同类型的数据元素。数组具有固定的大小&#xff0c;一旦创建后&a…

分享几个丝滑oled代码

最近一段业余时间在捣鼓esp32&#xff0c;发现对于一个搞diy的来说&#xff0c;它的生态&#xff0c;不管是开发环境、氛围还是可玩度都是独一挡的&#xff0c;国内外基于此的扩展真是太多了&#xff0c;找了几个通过按键/旋钮进行0.96寸OLED控制的案例&#xff0c;超级丝滑&am…

SQL中的各种连接的区别总结

前言 今天主要的内容是要讲解SQL中关于Join、Inner Join、Left Join、Right Join、Full Join、On、 Where区别和用法&#xff0c;不用我说其实前面的这些基本SQL语法各位攻城狮基本上都用过。但是往往我们可能用的比较多的也就是左右连接和内连接了&#xff0c;而且对于许多初学…

JavaSE-02笔记【封装~this和static】

文章目录 1.封装&#xff08;掌握&#xff09;1.1 封装的理解1.2 不封装存在的问题1.3 怎么封装1.4 难点解惑1.5 练习 2. this 和 static2.1 this&#xff08;掌握&#xff09;2.1.1 this是什么2.1.2 this 在实例方法中使用2.1.3 this访问实例变量2.1.4 this扩展①2.1.5 this扩…

量子算法入门——3.狄拉克符号与量子态(2)

2. 光的极化和S-G实验 光的极化&#xff1a;表达出一方向电场的振动方式 S-G实验 银原子内部介绍 S-G实验过程 在炉子中将银原子高温灼烧&#xff0c;高温使得银原子具有极大的动能&#xff0c;从炉口向四周发射出来&#xff0c;炉口前设置两个小门构成两点一线&#xff…

Centos7挂载磁盘

1 查看未挂载的磁盘 命令&#xff1a; fdisk -l红框圈中的即是本次要挂载的磁盘&#xff0c;/dev/vdb 与 /dev/vda 相比&#xff0c;其没有下方的 /dev/vda1 等信息&#xff0c;代表 /dev/vdb 磁盘并没有进行过分区操作&#xff0c;是一个新加的硬盘。 2 对新建的磁盘进行分…

基于SSM的宁夏旅游网站平台(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的宁夏旅游网站平台&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring …

K8S之运用污点、容忍度设置Pod的调度约束

污点、容忍度 污点容忍度 taints 是键值数据&#xff0c;用在节点上&#xff0c;定义污点&#xff1b; tolerations 是键值数据&#xff0c;用在pod上&#xff0c;定义容忍度&#xff0c;能容忍哪些污点。 污点 污点是定义在k8s集群的节点上的键值属性数据&#xff0c;可以决…