【并发编程】-1. 计算机内存架构、JAVA内存模型、Volatile关键字

JAVA内存模型JMM

概述

  1. 概念Java Memory Model (JMM)JAVA内存模型是一种抽象的概念,描述的是一组规范,规范中定义了程序中各个变量(实例字段、静态字段、数组对象的组成元素)的访问方式,决定了一个线程对共享变量的写入何时对另一个线程可见;
  2. 工作流程:
    1. JVM运行程序的实体是线程,在线程创建时,JVM都会为其分配工作内存,用于存储线程私有的数据;
    2. JMM规定所有变量都存储在主内存中,所以当线程想操作变量时,需要先将变量从主内存中拷贝进自己的工作内存中,然后再对变量进行操作,操作完成后再将变更后的值刷写回主内存中;
    3. 结合JVM,也就是当线程操作一个对象时,会根据工作内存中引用地址去找到主内存中的真实对象,然后会讲对象拷贝到自己的工作内存中,当操作的对象较大时,会进行选择性拷贝,只拷贝自己需要操作的那部分数据;

主内存

  1. 所属区域:属于线程共享区域,对JVM来说,主内存包括了堆和方法区;
  2. 存储内容:主要存储的是类的成员变量、方法中的局部变量、共享类的信息、常量、静态变量、线程创建的实例对象等共享数据都会放到主内存中,栈上分配的对象除外;
  3. 当多条线程对同一数据进行非原子性操作时,就可能出现线程安全问题

工作内存

  1. 所属区域:属于线程私有区域,对JVM来说,工作内存包括了程序计数器、虚拟机栈和本地方法栈;
  2. 存储内容:主要存储当前方法的所有本地变量信息;
  3. 工作内存是每个线程的私有数据,线程之间无法相互访问,所以不存在线程安全问题

主内存和工作内存的关系

  1. 数据存储类型

    1. 工作内存:对一个实例对象的成员方法来说,如果方法中含有的局部变量为boolean、byte、short、char、int、long、float、double八大基本数据类型,则这些数据将直接存储在工作内存的栈帧结构的局部变量表中,引用类型的局部变量则是存储对象的引用地址;
    2. 主内存:存储具体的实例对象、实例对象的所有成员字段、类的相关信息、static静态变量;
  2. 数据操作方式

    1. 主内存

      public class Test {
          Integer num = new Integer(100);
          private void add(){
              num++;
          }
      }
      

      在这里插入图片描述

    2. 工作内存

      public class Test {
          private void add(){
              Integer num = new Integer(100);
              num++;
          }
      }
      
      

      在这里插入图片描述

计算机内存架构

JAVA程序是运行在操作系统上的,它的所有操作最终都是在与操作系统交互,想要理解JAVA内存模型,需要对计算机内存架构有一定的了解;

架构图

在这里插入图片描述

  1. 多核CPU 现在的CPU一般都为多核CPU拥有多个核心,可以支持多任务并发执行,每个线程也最终也是映射到各个CPU核心上去执行的;

    1. 超线程技术:增强核心并行运算性能,它允许一个CPU执行多个控制流,工作原理是将一颗物理CPU虚拟化为两颗逻辑CPU,我们常说的4核8线程就是通过这个技术实现的;

      在这里插入图片描述

  2. 多级缓存

    1. 问题:由于内存的处理速度远低于CPU,导致CPU在处理指令时大量时间花费在等待内存准备数据上,从而影响CPU性能;
    2. 解决办法:是在寄存器和主内存之间添加L1、L2、L3多级高速缓冲区,来缓存CPU频繁访问的数据,之后寄存器再需要获取数据就可以直接从高速缓冲区中获取,不需要去访问内存;

CPU缓存一致性

由于CPU为了提升性能使用了多核与多级缓存等技术,那么各各核心、各级缓存之间就可能出现数据不一致的情况,CPU主要通过以下集中方式来保证缓存的一致性;

数据的写入

  1. 写直达:在数据写入前判断数据是否已经在Cache中,如果数据存在,则将Cache中的数据更新,然后将数据写入内存中

    这种方式无论数据在不在 Cache 里面,每次写操作都会写回到内存,这样写操作将会花费大量的时间,影响性能;

  2. 写回:当发生写操作时,只有在Cache不命中且数据对应的Cache中的 Cache Block 为脏标记情况下,才会将数据写到内存中;

    这种方式可以减少对主内存的写操作次数,提高性能。但是,可能会导致缓存中的数据与主内存不一致,需要额外的机制(如缓存一致性协议)来维护数据一致性。

    在这里插入图片描述

缓存一致性问题

  1. 问题:现在的多核CPU,由于L1/L2 Cache是多个核心各自独有的,而且CPU为了考虑性能,数据写入采用的是写回策略,这就可能导致多个核心缓存中数据不一致的情况,从而造成结果错误;

    在这里插入图片描述

    1. 例子: 如上图,假设,A读取了内存中的 i 变量,并执行了i++语句,由于使用了写回策略;
      • A就会先把执行结果i = 1写入到 L1/L2 Cache 中,然后把L1/L2 Cache中对应的 Block 标记为脏的,此时数据并没有被同步到内存中的,因为写回策略,只有在 A 中的这个 Cache Block 要被替换的时候,数据才会写入到内存里,这就出现了内存中的数据和A中Cache数据不一致的情况;
      • 如果这时旁边的 B 从内存读取 i 变量的值,则读到的将会是错误的值;
      • 这个就是所谓的缓存一致性问题,A 号核心和 B 号核心的缓存,在这个时候是不一致,从而会导致执行结果的错误;
  2. 解决思路:解决这个问题就需要对两个不同核心中的缓存数据进行同步,一般需要满足两点:

    1. 写传播:某个CPU核心里的Cache数据更新是,需要传播到其他核心的Cache中;

    2. 事物串行化:在某个CPU里对数据的操作顺序,必须在其他核心看起来顺序是一样的;

      1. 没有事物串行化存在的问题:A把 i 的值改为100,此时在同一时间 B 把 i 值改为200,这两个修改都会传播到C、D,此时就可能出现C、D收到A、B两个数据更改的顺序不同,也就会导致各个Cache中的数据不一致;所以,我们要保证 C 、 D能看到相同顺序的数据变化,比如变量 i 都是先变成 100,再变成 200,这样的过程就是事务的串行化;

      在这里插入图片描述

      1. 实现技术

        • CPU 核心对于 Cache 中数据的操作,需要同步给其他 CPU 核心;
        • 需要引入锁的概念呢,相当于只有获取到锁才能进行对应的数据更新;

总线嗅探

  1. 作用:可以实现写传播;
  2. 实现:当A修改了Cache中的i变量时,会通过总线将这个事件广播通知给其他核心,CPU的所有核心,都会监听总线上的广播事件,检查Cache是否有相同变量,如果有则会更新自己的Cache
  3. 缺点:不能保证事物串行化,需要频繁发送广播事件,加重了总线带宽压力;

MESI协议

  1. 概念将数据用四种状态来标记M(Modified|已修改) E(Exclusive|独占) S(Shared|共享) I(Invalidated|已失效)

    1. M(Modified)已修改:表示Cache中的数据已经被更新过,但还没写入到内存中;
    2. E(Exclusive)独占:表示数据值存储在一个CPU核心中,不需要考虑缓存一致性问题;
    3. S(Shared)共享:表示数据存储在多个CPU核心中,当我们需要更新数据时,需要向其他核心发送广播请求,要求其他核心将Cache中对应的数据标记为已失效状态,然后再更新;
    4. I(Invalidated)已失效:表示Cache中的数据已经失效,不可读取该状态数据;
  2. 相互之间的转化

    1. A从内存中读取变量 i 的值,会通过总线发消息通知其他CPU核心,如果其他CPU核心中没有缓存该数据,则A的Cache中 i 变量的标记为独占状态

    2. 之后,若其他核心中任意一个也要读取 i 变量,假设是B,则会通过总线发送广播消息,给其他核心,由于A中已经读取了i 变量,所以会把数据返回给B,并将核心中的 i 变量标记为共享状态

    3. 如果A这时要修改变量 i 的值,并且Cache中变量 i 的标记为共享状态,则会通过总线发送广播消息,将各个核心中的 i 变量标记为失效状态,然后将A的Cache中 i 变量标记为修改状态

    4. 若此时A继续修改 i 变量的值,则不需要通知其他核心,直接修改即可

    5. B需要读取i数据时,发现Cache中变量 i 的标记为失效,会发出读取数据的请求,A在收到B读取数据的请求后会将变量 i 同步到内存中,并将状态设为共享,这是B就可以读取到最新数据;

      在这里插入图片描述

  3. 带来的优点:当数据标记为修改或者独占时,修改更新数据不需要发送广播消息,在一定程度上减小了总线带宽压力;

操作系统与JMM之间的关系

  1. JMM只是一组抽象的概念,是一组规则,它的内存划分:工作内存(线程私有)、主内存(线程共享)对于计算机硬件来说并不存在;
  2. JMM的数据操作对应到底层也是操作主内存、操作高速缓存来操作数据的,而多核CPU有是通过MESI协议来达到数据一致性的,所以,JAVA内存模型底层其实也还是通过MESI一致性来保证的数据一次性

JMM原理

指令重排序

为了提高性能,编译器和处理器通常会对指令进行重排序,有如下三种:

编译器指令优化的重排

  1. 编译器在不改变程序语义的前提下,重新安排语句的执行顺序;

    1. 不改变语义即代码之间不存在依赖,依赖可分为两种:数据依赖int a = 1; int b = a;条件依赖boolean f = ture; if(f){};
  2. 例子:如下代码我们的预期应该是得到x=0、y=0这个结果,但实际上可能出现x=2、y=1这种结果;

    // 主存的共享变量
    int a = 0;
    int b = 0;
    
    //代码的顺序
    //线程A                   线程B
    代码1int x = a;         代码3int y = b;
    代码2:b = 1;             代码4:a = 2;
    
    //经过重排序后最终执行可能出现的顺序
    //线程A                   线程B
    代码2:b = 1;         代码4:a = 2;
    代码1int x = a;     代码3int y = b;  
    

指令并行的重排

  1. 现代处理器一般都的是采用指令级并行技术,会将多条不相互依赖(即后一个执行的语句,无需依赖前面语句的执行结果)的指令重叠执行,如CPU的流水线技术
CPU流水线
  1. 指令执行一般分为如下步骤:

    1. IF取指:CPU 会根据程序计数器里的存储地址,从内存或缓存中取出待执行的指令,将其加载到指令寄存器中;

    2. ID译码和取寄存器操作数:指令译码单元将指令解析成操作码和操作数,并确定执行指令所需的资源;

    3. EX执行或者有效地址计算:处理器根据指令的操作码执行相应的操作,可能涉及算术逻辑运算、内存访问或控制流操作等;

    4. MEM存储器访问:如果指令涉及内存操作,处理器会在这个阶段进行内存读取或写入操作;

    5. WB写回:将执行阶段得到的结果写回到寄存器文件或内存中;

      在这里插入图片描述

  2. 为了提高硬件的利用率,CPU执行指令会采用流水线技术来工作;图中可以看出,当指令1还未执行完成时,第2条指令便利用空闲的硬件开始执行,这样能提升CPU性能;

    在这里插入图片描述

  3. 存在的问题:当流水线出现中断,所有的硬件设备都会进入一轮停顿期;

    1. 比如说需要执行i = a + b; j = c + d;不存在指令重排序时他们的执行顺序:1.读取a、2.读取b、3.执行a+b、4.保存结果到i、5.读取c、6.读取d、7.执行c+d、8.保存结果到j;
    2. 我们可以知道3、4两个步骤依赖于1、2两步,当1、2两步卡顿时,会导致3进行等待,则4也需要进行等待,最终拖慢整个流水线的执行,5、6、7、8同理;
    3. 解决的办法:我们可以知道第3步与5、6不存在依赖关系,所以CPU可以通过指令重排序的方式将5、6步的执行提前,执行顺序变为1、2、5、3、6、4、7、8,这时下相当于给具有依赖关系的语句中间插入无关语句,从而尽量保证前面的指令执行完成,减少停顿的可能;
  4. 作用:根据上述,我们可以知道,指令重排序的作用时减少CPU在流水线执行时的停顿;

  5. 带来的问题:对于单线程而言,由于指令重排是在保证串行语义执行的一致性的情况下进行的,但对于多线程环境就可能导致程序乱序执行的问题;

内存系统的重排

  1. 处理器缓存的存在,可能导致内存与缓存数据的同步存在时间差,导致加载load和存储store操作看上去可能是在乱序操作;

三大特性

JMM主要是围绕程序执行的原子性、有序性、可见性来开展的,通过这三大特性来保证数据的并发安全;

原子性

  1. 概念:指操作的原子性,指一组操作要么全部成功要么全部失败;
  2. 对于32位系统而言,byte、short、int、float、boolean、char等基本数据类型的读写操作是原子操作,而lone、double存储大小为64bit,一次读写操作需要分两次读取数据,就可能出现数据被两个线程分两次读取的情况;

可见性

  1. 概念:当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值;
  2. 由上述指令重排序可知,编译器还是处理器的重排现象,在多线程环境下会导致乱序执行的情况出现,也可能导致可见性问题;

有序性

  1. 概念:指代码按照我们编写的顺序从上往下依次执行;
  2. 在多线程环境下,对于某个线程来说,他的所有操作都视为有序的,但如果从一个线程中观察另外一个线程,所有操作都是无序的,原因是指令重排序和主内存与工作内存之间的同步存在延迟;

JMM如何保证三大特性

  1. 原子性:除了基本数据类型的操作本身就保证原子性外,对于方法或代码块级别的原子性操作可以使用synchronize关键字或Lock锁接口实现类来保证程序执行的原子性;
  2. 可见性:工作内存和主内存同步延迟现象导致的可见性问题,可以通过加锁或者volatile关键字解决;
  3. 有序性:通过加锁或者volatile关键字解决,volatile可以通过禁止指令重排序来保证有序性;
  4. JMM内部还定义一套happens-before原则来保证多线程环境下两个操作间的原子性、可见性以及有序性

JMM中的happens-before原则

线程与内存的交互

  1. 交互类型:在JAVA程序在执行过程中,实际就是OS在调度JVM的线程执行,执行过程就是与内存的交互操作,而内存的交互操作有8种;
    1. lock锁定:作用于主内存的变量,将一个变量标识为线程独占状态;
    2. unlock解锁:作用于主内存的变量,将一个锁定状态的变量释放出来;
    3. read读取:作用于主内存的变量,将一个变量的值从主内存传输到线程的工作内存中;
    4. load载入:作用于工作内存的变量,将read操作从主内存中传输的值放入工作内存中;
    5. use使用:作用于工作内存的变量,将工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到的变量值,就会使用到这个命令;
    6. assign赋值:作用于工作内存的变量,把一个从执行引擎中接受到的值放入工作内存的变量副本中;
    7. store存储:作用于主内存的变量,把一个工作内存的一个变量值传送到主内存中;
    8. write写入:作用于主内存的变量,把store操作传送的值放入主内存的变量中;
  2. JMM指定的交互规则
    1. 不允许read和load、store和write操作之一单独出现;即:使用了read必须load,使用了store必须write;
    2. 不允许线程丢弃他最近的assign操作,即工作变量的数据改变后,必须告知主内存;
    3. 不允许线程将没有assign操作的数据同步回主内存;
    4. 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assignload操作;
    5. 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁;
    6. 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
    7. 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量;
    8. 对一个变量进行unlock操作之前,必须把此变量同步回主内存;

JMM中的happens-before原则

  1. 程序顺序原则:即在一个线程内必须保证语义串行性,也就是按照代码顺序执行;
  2. 锁规则:解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前;
  3. volatile规则volatile变量的写,先发生于读,这保证了volatile变量的可见性;简单的理解就是:volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值;
  4. 线程启动规则:线程的start()方法先于它的每一个动作,即如果线程A,在执行线程B的start方法前修改了共享变量的值,那么当线程B执行start方法时,线程A变更过的共享变量,对线程B可见;
  5. 传递性优先级规则:A先于B,B先于C,那么A必然先于C;
  6. 线程终止规则:线程的所有操作先于线程的终结,Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见;
  7. 线程中断规则:对线程interrupt()方法的调用,先发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断;
  8. 对象终结规则:对象的构造函数执行,结束先于finalize()方法;

Volatile关键字

volatileJAVA提供的轻量级的同步工具,它可以保证可见性和做到禁止指令重排序做到有序性,但不能保证原子性;

内存屏障(Memory Barrier)

  1. 存屏障是一个CPU指令,作用是保证特定操作的执行顺序和保证某些变量的内存可见性;
    1. 保证执行顺序:具体的就是在指令直接插入MemoryBarrier内存屏障,相当于告诉编译器和CPU不管什么指令都不能与这条MemoryBarrier进行指令重排序,也就是通过插入内存屏障,禁止在内存屏障前后的指令执行重排序;
    2. 保证可见性:强制刷出各种CPU缓存数据,使CPU上的任何线程都能读到这些数据的最新版本;
  2. 内存屏障的类型
    1. LoadLoad Barriers:确保Load1指令数据的装载,发生于Load2及后续所有装载指令的数据装载之前;
      1. 指令示例:Load1; LoadLoad; Load2;
    2. StoreStore Barriers:确保Store1数据的存储对其他处理器可见(刷新到内存中)并发生于Store2及后续所有存储指令的数据写入之前。
      1. 指令示例:Store1; StoreStore; Store2;
    3. LoadStore Barriers:确保Load1指令数据的装载,发生于Store2及后续所有存储指令的数据写入之前。
      1. 指令示例:Load1; LoadStore; Store2;
    4. StoreLoad Barriers:确保Store1数据的存储对其他处理器可见(刷新到内存中),并发生于Load2及后续所有装载指令的数据装载之前;StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载)完成之后,才执行该屏障之后的内存访问指令;
      1. 指令示例:Store1; StoreLoad; Load2;

内存可见

  1. volatile可以保证,一个线程对volatile所修饰的变量进行更改操作后,总能对其他线程可见;
  2. 当一个变量被volatile修饰,发生写操作时JMM会把该线程工作内存中的共享变量值刷新到主内存中;当读取操作时,JMM会把该线程对应的工作内存置为无效,要求该线程从主内存中重新读取该变量的值,也是通过内存屏障实现的

禁止指令重排序

  1. volatile通过在修饰变量访问前后添加内存屏障,来静止指令重排序,从而保证有序性;

  2. 例子:说明volatile禁止指令重排序,synchronized不禁止;

    1. 以下是一个双重锁检测的代码:

      public class Singleton{
        private static Singleton singleton;
        
        private Singleton(){}
        
        public static Singleton getInstance(){
           if(singleton == null){
                synchronized(Singleton.class){
                      if(singleton == null){
                            singleton = new Singleton();
                     }
                }
            }
            return singleton;
        }
      }
      
      
    2. singleton没有被volatile修饰的时候是可能获取到null值,出现线程不安全的情况,,原因如下:

      在这里插入图片描述

    3. 但当如果singleton变量加上volatile后,会禁止new这个操作被其他线程打断,从而保证线程安全;

具体实现

  1. 具体表现:假设有五个相互之间不存在依赖性的指令操作A、B、C、D、E,现在对B、C、D加上内存屏障就变成了A、内存屏障( B、C、D)内存屏障、E;此时依旧可以发生重排序,但会将(B、C、D)看作一个整体,再去与A、E排序,其内部也可以重排序;
  2. 实现方式volatile并没有直接使用操作系统的内存屏障指令,而是使用的JVM内存屏障字节码指令,JVM的内存屏障字节码指令会间接使用操作系统的内存屏障指令,也就是JVM对操作系统的内存屏障指令做了层封装,具体的定义位于HotSport源码的bytecodeInterpreter.cpp文件中;
  3. 实现原理:通过内存屏障的StoreLoad读+写屏障实现
    1. 当多个线程读取共享变量时,触发读屏障,读屏障中会记录哪些线程读取了这个变量
    2. 当有一条线程写会数据时,就会触发写屏障,此时写屏障会根据前面读屏障记录下来的线程,去通知所有还未刷回的线程,重新再来获取一次最新的值;(保证了可见性)
    3. 硬件层面
      1. volatile修改的高速缓存数据,写回到机器内存时,会通过缓存一致性协议和总线嗅探技术,通知其他处理器来检查这个变量有没有在自己的缓存中,如果缓存了该变量的数据则将该数据置为无效,后续再需要使用次变量时,重新从内存中读取;

不满足原子性

  1. 假设现在有两个线程T1、T2此时内存中i = 1,现在两个线程都执行i++操作,大致操作如下:
    1. T1-读值、T1-计算、T1-写值;
    2. T2-读值、T2-计算、T2-写值;
  2. 他们的每一步操作都是原子的,但整体不是,这就会导致当两条线程并行时,语句的执行可能变为(T1-读值、T2-读值、T2-计算、T2-写值、T1-计算、T1-写值),最终的结果为i = 2不符合预期的i = 3
  3. 现在假设使用了volatile来修饰变量,假设还是上面的执行流程,当T2写值时,会触发内存屏障,此时会要求还未刷回数据的T1线程重新获取一次主存数据,也就是i = 2,再经过计算后写入主存,最终结果为i = 3符合预期;
    1. 从步骤三来看,似乎volatile解决线程安全问题,但其实步骤三是需要建立在T1、T2处与同一核心的情况;对于多核CPUT1、T2线程可以绑定不同核心,从而达到并行执行的效果,此时就可能出现T1、T2i++操作,在同一时刻并发执行,接着出现T1、T2同时将i==2这个结果,刷写回主内存情况,从而导致线程安全问题;

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

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

相关文章

八皇后00

题目链接 八皇后 题目描述 注意点 每个皇后都不同行、不同列,也不在对角线上“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线 解答思路 本题与N皇后相同,思路仍然是深度优先遍历的同时存储前面每一行选取了哪些列&#…

深圳网页设计收费情况

深圳是中国最具活力和发展速度最快的城市之一,随着经济的快速发展,各种行业都飞速发展,尤其是互联网行业。网页设计是互联网行业的重要组成部分,深圳的网页设计师数量也是非常庞大的。那么,深圳网页设计师的收费情况是…

LLM 推理:Nvidia TensorRT-LLM 与 Triton Inference Server

随着LLM越来越热门,LLM的推理服务也得到越来越多的关注与探索。在推理框架方面,tensorrt-llm是非常主流的开源框架,在Nvidia GPU上提供了多种优化,加速大语言模型的推理。但是,tensorrt-llm仅是一个推理框架&#xff0…

互联网信息服务算法备案流程与要求

一、备案申请的办理流程 企业通过网信办的互联网信息服务算法备案系统(https://beian.cac.gov.cn/#/index)提交算法备案申请。填报信息包括三部分,分别是算法主体信息、产品及功能信息、算法信息。备案中比较重要的文件包括主体信息中的《落…

如何查看websocket连接信息

Chrome 浏览器中查看 webSocket 连接信息_谷歌浏览器看不到 websocket-CSDN博客 Getting Started — Flask-SocketIO documentation 运作原理 | Socket.IO

Oracle数据库使用指南基本概念

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…

不锈钢氩弧焊丝ER316L

说明:TG316L 是超低碳的不锈钢焊丝。熔敷金属耐蚀、耐热、抗裂性能优良。防腐蚀性能良好。 用途:用于石油化工、化肥设备等。也可用于要求焊接后不进行热处理的高Cr钢的焊接。

Android模拟器linux内核的下载,编译,运行,驱动开发测试

Android模拟器linux内核的下载,编译,运行,内核模块开发 1.下载适合Android模拟器的内核 git clone https://aosp.tuna.tsinghua.edu.cn/android/kernel/goldfish.git git branch -a git checkout android-goldfish-4.14-gchips 新建一个目录…

Word恢复历史文档,记好4个方法就足够

“我正在准备一个重要的报告,但是电脑突然就崩溃了,导致我的文档还没保存就被关闭了,大家有什么方法可以恢复Word历史文档吗?快给我出出主意吧!” 在数字化时代,文档编辑和保存已经成为我们日常工作和学习中…

flink使用StatementSet降低资源浪费

背景 项目中有很多ods层(mysql 通过cannal)kafka,需要对这些ods kakfa做一些etl操作后写入下一层的kafka(dwd层)。 一开始采用的是executeSql方式来执行每个ods→dwd层操作,即类似: def main(…

信创产业生态圈各企业分布

文章目录 应用系统:办公管理:云平台网络安全基础软件操作系统数据库中间件 基础硬件芯片 我们国家在前几年提出了信创战略计划,就是为了在信息技术领域,将一些国外牌子的设备和应用、软件逐渐替换成国产的,保证国家的金…

记录samba账号操作日志,增删改查等(安全审计)

说明:windows用户映射samba文件共享服务,记录samba账号的操作日志 只要三步! 安装必要软件包 audit配置samba共享配置Syslog 具体步骤 1. 安装必要的软件包 audit 是linux系统的高级审计框架 主要功能:系统调用监控、文件和目…

微前端框架是为了解决项目应用在大型项目中带来的复杂性和维护难题而提出的技术方案。

微前端框架是为了解决单页应用(SPA)在大型项目中带来的复杂性和维护难题而提出的技术方案。Qiankun.js、MicroApp 和 Wujie 是三种流行的微前端框架。以下是对这三种框架的优缺点分析: Qiankun.js 优点 成熟度高:Qiankun.js 基…

34534534

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话: 知不足而奋进,望远山而前行&am…

如何高效使用 .http 文件记录和测试API接口

1. 前言 在现代软件开发中,API(应用程序接口)成为了系统间通信的重要桥梁。.http 文件作为一种轻量级的API请求描述方式,不仅便于开发者记录和分享API接口信息,还能够帮助自动化测试流程。本文将深入介绍如何有效地使…

1. jenkins持续集成交付

jenkins持续集成交付 一、jenkins介绍二、jenkins的安装部署1、下载jenkins2、安装jenkins3、修改插件下载地址4、初始化jenkins 一、jenkins介绍 持续集成交付, CI/CD 偏开发、项目编译、部署、更新 二、jenkins的安装部署 1、下载jenkins [rootjenkins ~]# wge…

公交车载视频监控系统概述

引言 随着城市交通的不断发展,公交车作为城市公共交通的重要组成部分,其安全性和管理效率越来越受到关注。为了提升公交车运营的安全性和管理效率,基于索迪迈视频监控管理平台,结合3G/4G网络技术,我们构建了一套公交车…

【BES2500x系列 -- RTX5操作系统】深入探索CMSIS-RTOS RTX -- 同步与通信篇 -- 信号量和互斥锁 --(三)

💌 所属专栏:【BES2500x系列】 😀 作  者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &#x1f49…

《mysql》--mysql约束

数据库约束 有的时候数据库中的数据是有一定要求的,有些数据认为是合法数据,有些是非法数据,如果靠人工检查显然是不靠谱的; 数据库会自动的对数据的合法性进行校验检查目的就是,保证数据中能够避免被插入/修改一些非…