当前位置: 首页 > news >正文

JMM内存模型

JMM内存模型

  • JMM内存模型
    • 定义
      • 三大特性
        • 原子性
        • 可见性
        • 有序性
      • volatile
        • 语义
        • JMM规则
        • 操作系统实现
          • 术语
          • 缓存一致性要求
          • 缓存一致性机制
          • 写传播
          • 事务串行化
        • 重排序
          • as-if-serial 语义(像是有序的)
          • happens-before 原则
          • happens-before 原则的八大子原则
          • 内存屏障
          • 总结
        • final
          • final域为基本数据类型
            • 写final域的重排序规则
            • 读final域的重排序规则
          • final域为引用类型
          • final引用不能从构造函数中"逸出"
          • JSR-133为什么要增强final语义
        • 补充
          • 八大原子操作
          • 八大原子操作示意图
          • 八大原子操作规则
          • 总线嗅探
          • LOCK操作、LOCK指令、LOCK# 信号
          • 缓存行
          • MESI
          • 编译器
          • 处理器
          • 间接依赖关系

JMM内存模型

本文章为个人总结,如有疑惑点,欢迎互相学习。

定义

JMM内存模型规定了一个线程如何以及何时可以看到其他线程修改过后的共享变量的值,以及如何同步地访问获取共享变量值。围绕原子性、有序性、可见性三大特性展开。

前提:对于硬件,所有的线程栈和堆都分布在主内存中。部分线程栈和堆可能有时候会出现在CPU缓存中和CPU内部的寄存器中

三大特性

原子性

JMM内存模型定义了八种原子性操作来完成主内存与工作内存之间的交互,并且操作必须满足多条规则保证操作的顺序性,但是并未规定操作必须连续执行。所以在单线程程序执行时,JMM可以保证执行结果与顺序一致性模型中的结果相同。但是在多线程程序执行时,会有可见性以及有序性问题。

可见性

CPU与主存运行速度的差异所以引入高速缓存(工作内存),但是高速缓存的引入也让多线程访问共享变量多了可见性的问题。多线程程序执行时候,一个线程修改了共享变量的值时,其他线程需要感知到这个修改,如果当前自己自己有使用,就需要做相应操作。

有序性

编译器和处理器进行优化导致的重排序问题,因为程序中的操作都不是原子性的,例如new一个对象,对其申请内存、赋值等一系列复杂操作不是整体的原子操作,所以对指令优化可以对不影响结果的前提下进行指令重排序,例如多个变量统一申请内存,可以增加单线程执行效率。但是多线程执行时候,指令重排序会造成执行结果不同。

volatile

语义

可见性:对一个volatile变量的读,总是能看到对这个变量最后的写入
原子性:对任意单个volatile变量的读/写具有原子性,但类似于i++这种复合操作不具有原子性
有序性:对volatile修饰的变量的读写操作前后加上各种特定的内存屏障来禁止重排序

JMM规则

volatile修饰的变量的read、load、use操作和assign、store、write必须是连续的,即修改后必须立即同步回主内存,使用时必须从主内存刷新,由此保证操作volatile变量是多线程的可见的。

操作系统实现

术语
  • 缓存锁定: 通过Lock前缀指令,会锁定变量缓存行区域并写回主内存。
  • 缓存一致性机制: 阻止 同时 修改被两个以上处理器缓存的内存区域数据。一个处理器的缓存回写会导致其他处理器的缓存无效。
  • 伪共享: 如果多个核的线程在操作同一个缓存行中的不同变量数据,那么就会出现频繁的缓存失效,即使在代码层面看这两个线程操作的数据之间完全没有关系。这种不合理的资源竞争情况就是伪共享。
    • Jdk1.7时,可以添加空变量进行缓存行填充
    • Jdk1.8时,添加注解以及配置jvm参数可以解决伪共享
缓存一致性要求
  • 写传播:一个处理器对于某个内存位置所做的写操作,对于其他处理器是能感知到的。

例如a、b两个处理器对位置x的读取,当c处理器对位置x写操作后。a和b需要能感知到,并对位置x的值读取结果为c处理器写入后的值。这就是写传播。

  • 事务串行化:对同一内存单元的所有写操作都能串行化。即所有的处理器能以相同的次序看到这些写操作。

当a、b处理器对位置x的读取,c和d处理器对位置x顺序写操作后,a、b处理器对位置x的值读取结果应为d处理器最后写入的值,不能是c处理器的值,这就是事务串行化。

缓存一致性机制
写传播
  • 窥探机制: 总线嗅探广播形式,所有处理器能感知到所有活动

    • 优点:速度更快
    • 缺点:不可扩展,随着系统变大,总线的大小以及带宽必须增加
  • 基于目录的机制: 点对点,总线事件只会发给感兴趣的 CPU (借助 directory)。>64处理器使用这种类型的缓存一致性机制

    • 优点:使用更少带宽
    • 缺点:有更长的延迟
事务串行化
  • 总线仲裁机制

总线会同步试图并发使用总线的事务。在一个处理器执行总线事务期间,总线会 禁止其他的处理器和I/O设备执行内存的读/写。总线的这种工作机制可以把所有处理器对内存的访问以串行化的方式来执行。在任意时间点,最多只能有一个处理器可以访问内存。这个特性确保了单个总线事务之中的内存读/写操作具有原子性。处理器会自动保证基本的内存操作的原子性,也就是一个处理器从内存中读取或者写入一个字节时,其他处理器是不能访问这个字节的内存地址。同时处理器提供总线锁定缓存锁定两个机制来保证复杂内存操作的原子性。

  • 总线锁定

总线锁定就是使用处理器提供的一个 LOCK#信号,当其中一个处理器在总线上输出此信号时,其它处理器的请求将被阻塞住,那么该处理器可以独占共享内存。

  • 缓存锁定

1、Lock操作期间锁定缓存行范围
2、不会在总线上声言LOCK#信号,也就不会进行总线锁定
3、使用缓存一致性协议通知其他处理器缓存失效:MESI

缓存锁定不能用场景

  • 当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,则处理器会调用总线锁定。
  • 有些处理器不支持缓存锁定。目前大多数处理器都已支持缓存锁定。

重排序

概念: 只要程序的最终结果与顺序化执行的结果一致,那么执行的顺序可以与代码的顺序不一致,此过程叫做重排序。

1、编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句顺序。
2、指令级并行的重排序。现代处理器采用指令级并行技术(ISP)来将多条指令重叠执行。如果数据不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3、内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得store和load操作看上去可能是在乱序执行。

1:属于编译器重排序
2和3:属于处理器重排序

对于编译器,JMM的"编译器重排序规则"会禁止特定类型的"编译器重排序"(不是所有“编译器重排序”都被禁止)
对于处理器,JMM的处理器重排序会要求Java编译器在生成指定序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的"处理器重排序"

JMM属于语言级的内存模型,通过禁止特定类型的编译器和处理器重排序,提供一致的内存可见性。

as-if-serial 语义(像是有序的)

含义: 无论如何重排序,程序的最终执行结果是不能变的。
所以,在这个语义下,编译器 为了保持这个语义,不会去对存在有数据依赖关系的操作进行重排序,如果数据之间不存在依赖关系,这些操作就会被操作系统进行最大的优化,进行重排序,使得代码的执行效率更高。

happens-before 原则

Java并发编程必须要保证代码的原子性,有序性,可见性,如果只靠sychronized和volatile关键字来保证它,那么 程序员的代码 写起来就显的相当的麻烦,从JDK 5开始,Java使用新的JSR-133内存模型,提供了happens-before 原则来辅助保证程序执行的原子性、可见性以及有序性的问题,它是判断数据是否存在竞争、线程是否安全的依据。

happens-before 原则的八大子原则
  • 程序顺序原则: 在一个程序中的代码执行顺序是有序的,即使出现重排序,也会保证在单线程下的结果是一致的。
  • 锁原则: 在同一段程序中,要想对这段程序加锁,必须是前一段锁已经解锁了,才能对这段程序继续加锁。
  • volatile变量原则: 如果一个线程先去写一个volatile变量,那么它必须先去读这一个变量,所以,在任何情况下volatile修饰的变量的值在修改时都是多其他线程是可见的。
  • 线程启动原则: 在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。
  • 线程终止原则: 在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。
  • 线程中断原则: 对线程 interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断。
  • 传递性原则: 假设A先于B,B先于C,那么A必须先于C。
  • 终结原则: 一个对象的初始化完成先于他的finalize方法调用。
内存屏障
屏障类型指令示例说明
LoadLoadLoad1; LoadLoad; Load2保证load1的读取操作在load2及后续读取操作之前执行
StoreStoreStore1; StoreStore; Store2在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
LoadStoreLoad1; LoadStore; Store2在stroe2及其后的写操作执行前,保证load1的读操作已读取结束
StoreLoadStore1; StoreLoad; Load2保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行

此处的load和store是原子操作中的load操作和store操作。

总结

as-if-serial是语义。所有重排序规则的定义都基于此语义之上。
happens-before原则,是给到程序员的原则。在程序员编写代码时,无需考虑这些原则中的包含重排序规则。是对于编译器重排序的限制。
内存屏障,是实际落地的技术。在Java层面可以使用Unsafe中的 loadFence、storeFence、fullFence 进行手动添加内存屏障。对于处理器重排序的限制,

final

对于final域,编译器和处理器要遵守两个重排序规则。

  • 写final域的重排序规则:在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。(禁止把final域的写操作重排序到构造函数之外)
  • 读final域的重排序规则:初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
public class FinalExample {int i;//普通域final int j;//final域static FinalExample obj;public FinalExample () {i = 1;//写普通域。对普通域的写操作【可能会】被重排序到构造函数之外j = 2;//写final域。对final域的写操作【不会】被重排序到构造函数之外}// 写线程A执行public static void writer () { obj = new FinalExample ();}// 读线程B执行public static void reader () { FinalExample object = obj;//读对象引用int a = object.i;//读普通域。可能会看到结果为0(由于i=1可能被重排序到构造函数外,此时y还没有被初始化)int b = object.j;//读final域。保证能够看到结果为2}
}
final域为基本数据类型
写final域的重排序规则

写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则的实现包含下面2个方面。

  • JMM禁止编译器把final域的写重排序到构造函数之外。
  • 编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。

对于前面的程序,下面是一种可能的执行序列
在这里插入图片描述

在上图的执行序列中,写普通域的操作(i=1)被编译器重排序到了构造器之外,导致读线程B错误的读取变量i的初始化的值。而由于写final域重排序规则的限定,写final域的操作(j=2)被限定在了构造函数之内,读线程B必然能够正确读到正确的值。

读final域的重排序规则

对读final域的重排序规则的实现,包括以下2个方面

  • 在一个线程中,初次读 “对象引用” 与初次读 该对象"包含的final域"。JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)
  • 编译器会在读final域操作的前面插入一个LoadLoad屏障。

下面是前面程序的另外一种执行序列(假设写线程A没有发生任何重排序,同时程序在不遵守间接依赖的处理器上执行)
在这里插入图片描述
在上图的执行序列中,读对象的普通域的操作被处理器重排序到读对象引用之前。读普通域时,由于该域还没有被写线程A写入,因此这是一个错误的读取操作。而由于读final域的重排序规则的限定,会把读对象final域的操作限定在读对象引用之后,由于此时final域已经被A线程初始化过了,所以这是一个正确的读取操作。

final域为引用类型

而对于final域是引用类型,写final域 的重排序规则对编译器和处理器增加了如下约束:

  • 在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
public class FinalReferenceExample {final int[] intArray;// final是引用类型static FinalReferenceExample obj;public FinalReferenceExample () {intArray = new int[1];// ①对final域的写入intArray[0] = 1;// ②对这个final域引用的对象的成员域的写入}// 写线程A执行public static void writerOne () {obj = new FinalReferenceExample (); // ③把被构造的对象的引用赋值给某个引用变量}// 写线程B执行public static void writerTwo () {obj.intArray[0] = 2;// ④}// 读线程C执行public static void reader () {if (obj != null) {// ⑤int temp1 = obj.intArray[0];// ⑥}}
}

对上面的示例程序,假设首先线程A执行writerOne()方法,执行完后线程B执行writerTwo()方法,执行完后线程C执行reader()方法。

下面是一种可能的线程执行时序。

在这里插入图片描述
JMM可以确保读线程C至少能看到写线程A在构造函数中对final引用对象的成员域的写入。即C至少能看到数组下标0的值为1,而写线程B对数组元素的写入,读线程C可能看得到也可能看不到。JMM不保证线程B的写入对读线程C可见,因为写线程B和读线程C之间存在数据竞争,此时的执行结果不可预知。

如果想要确保读线程C看到写线程B对数组元素的写入,写线程B和读线程C之间需要使用同步(锁或volatile)来确保内存可见性。

final引用不能从构造函数中"逸出"

写final域的重排序规则可以确保:在引用变量为任意线程可见之前,该引用变量指向的对象的final域已经在构造函数中被正确初始化过了。但是,要得到这个效果,还需要一个保证:在构造函数内部,不能让这个被构造对象的引用为其他线程所见,也就是对象引用不能在构造函数中“逸出”。

public class FinalReferenceEscapeExample {final int i;static FinalReferenceEscapeExample obj;public FinalReferenceEscapeExample () {i = 1;                   // ①写final域obj = this;              // ②this引用在此"逸出"}//写线程A执行public static void writer() {new FinalReferenceEscapeExample ();}//读线程B执行public static void reader() {if (obj != null) {      // ③int temp = obj.i;   // ④}}
}

假设线程A执行writer()方法,线程B执行reader()方法。这里的操作②使得对象还未完成构造前就为线程B可见。即使这里的操作②是构造函数的最后一步,且在程序中操作②排在操作①后面,执行read()方法的线程仍然可能无法看到final域被初始化后的值,因为这里的操作①和操作②之间可能被重排序。

实际的执行时序可能如下:
在这里插入图片描述

在构造函数返回前,被构造对象的引用不能为其他线程可见。因为此时final域可能还没有初始化。在构造函数返回后,任意线程都将保证能看到final域正确初始化之后的值。

JSR-133为什么要增强final语义

在旧的Java内存模型中,一个最严重的缺陷就是线程可能看到final域的值会改变。比如,一个线程当前看到一个整型final域的值为0(还未初始化之前的默认值),过一段时间之后这个线程再去读这个final域的值时,却发现值变为1(被某个线程初始化之后的值)。最常见的例子就是在旧的Java内存模型中,String的值可能会改变。

为了修补这个漏洞,JSR-133专家组增强了final的语义。通过为final域增加写和读重排序规则,可以为Java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用)就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。

补充

八大原子操作
  • lock(锁定): 作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
  • unlock(解锁)lo: 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取): 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入): 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用): 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值): 作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量, 每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储): 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
  • write(写入): 作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
八大原子操作示意图

在这里插入图片描述

lock和unlock是在锁定内存时候使用
read、load可以称为load操作
write、store可以成为save操作
save、load都有两步操作,可以理解为取值和赋值的过程,一个是作用于主内存,一个作用于工作内存

八大原子操作规则
  • 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者工作内存发起回写了但主内存不接受的情况出现。
  • 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或 assign)的变量,换句话说就是对一个变量实施use、store操作之前,必须先执行assign和load操作。
  • 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作以初始化变量的值。
  • 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。
总线嗅探

当特定数据被多个缓存共享时,处理器修改了共享数据的值,更改必须传播到所有其他具有该数据副本的缓存中。这种更改传播可以防止系统违反缓存一致性。

  • 写失效

    当处理器写入一个共享缓存块时,其他缓存中的所有共享副本都会通过总线窥探失效。MSI、MESI、MOSI、MOESI和MESIF协议属于该类型。

  • 写更新

    当处理器写入一个共享缓存块时,其他缓存的所有共享副本都会通过总线窥探更新。这个方法将写数据广播到总线上的所有缓存中。它比write-invalidate协议引起更大的总线流量。这就是为什么这种方法不常见。Dragon和firefly协议属于此类别。

LOCK操作、LOCK指令、LOCK# 信号

LOCK前缀指令:汇编语言层面。会锁总线,其它CPU对内存的读写请求都会被阻塞,直到锁释放。

LOCK# 信号:cpu提供信号。当一个cpu在总线上输出此信号时,其他cpu的请求将被阻塞,那么该cpu则独占共享内存。

LOCK操作:JMM层面。可以理解为声明这个操作的时候,会汇编为LOCK前缀指令,然后在cpu层面通过发送LOCK# 信号进行总线锁定。

缓存行

缓存行(cache line)是缓存读取的最小单元,缓存行是 2 的整数幂个连续字节,一般为 32-256 个字节,最常见的缓存行大小是 64 个字节。

MESI

MESI是以缓存行的几个状态来命名的(全名是Modified、Exclusive、 Share or Invalid)。该协议要求在每个缓存行上维护两个状态位,使得每个数据单位可能处于M、E、S和I这四种状态之一,各种状态含义如下:

  • M:被修改的。处于这一状态的数据,只在本CPU中有缓存数据,而其他CPU中没有。同时其状态相对于内存中的值来 说,是已经被修改的,且没有更新到内存中。
  • E:独占的。处于这一状态的数据,只有在本CPU中有缓存,且其数据没有修改,即与内存中一致。
  • S:共享的。处于这一状态的数据在多个CPU中都有缓存,且与内存一致。
  • I:无效的。本CPU中的这份缓存已经无效。

总结四种状态:可分为两种 独占(M和E)共享(S和I)。

  • 独占:M是只有本cpu有,而且缓存已被修改,与内存不一致;E是只有本cpu有,缓存未修改和内存一致。

  • 共享:S是多cpu缓存中都有,该缓存未修改与内存一致;I是多cpu缓存中都有,该缓存修改与内存不一致,该缓存失效。

一个处于M状态的缓存行,必须时刻监听所有试图读取该缓存行对应的主存地址的操作,如果监听到,则必须在此操作执行前把其缓存行中的数据写回CPU。

一个处于S状态的缓存行,必须时刻监听使该缓存行无效或者独享该缓存行的请求,如果监听到,则必须把其缓存行状态设置为I。

一个处于E状态的缓存行,必须时刻监听其他试图读取该缓存行对应的主存地址的操作,如果监听到,则必须把其缓存行状态设置为S。

编译器

Java编译器是开发人员用来编译Java应用程序的程序,它将Java代码转换为独立于平台的低级字节码,也就是常用的Java Class字节码。

例如:javac:sun公司编译器,jdk默认自带的编译器。eclipse编译器等

处理器

中央处理器(Central Processing Unit,简称CPU)。
例如:intel处理器、AMD处理器、M1处理器等。

间接依赖关系

初次读对象引用与初次读该对象包含的final域,这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系,因此编译器不会重排序这两个操作。但有少部分处理器允许对存在间接依赖关系的操作做重排序(例如alpha处理器)

相关文章:

JMM内存模型

JMM内存模型JMM内存模型定义三大特性原子性可见性有序性volatile语义JMM规则操作系统实现术语缓存一致性要求缓存一致性机制写传播事务串行化重排序as-if-serial 语义(像是有序的)happens-before 原则happens-before 原则的八大子原则内存屏障总结finalf…...

Linux- 系统随你玩之--玩出花活的命令浏览器-双生姐妹花

文章目录1、背景2、命令浏览器-双生姐妹花2.1、姐妹花简介2.2 、验名正身2.3、常用功能选项3、常用实操3.1、发送请求获取文件3.1.1、抓取页面内容到一个文件中3.1.2、多个文件下载3.1.3、下载ftp文件3.1.4、断点续传3.1.5、上传文件3.1.6、内容输出3.2 、利用curl测试接口3.3 …...

【深度学习】基于Hough变化的答题卡识别(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。⛳座右铭&#…...

Linux - 进程控制(创建和终止)

1.进程创建fork函数初识 在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。返回值:子进程返回0,父进程返回子进程id,出错返回-1getpid()获取子进程id&#xff0c…...

依赖注入~

依赖注入之setter注入: 依赖注入是IOC具体的一种实现方式, 这是针对资源获取的方式角度来说的,之前我们是被动接受,现在IOC具体的实现叫做依赖注入,从代码的角度来说,原来创建对象的时候需要new&#xff0…...

【嵌入式硬件芯片开发笔记】HART协议调制解调芯片AD5700配置流程

【嵌入式硬件芯片开发笔记】HART协议调制解调芯片AD5700配置流程 XTAL_EN接地,CLK_CFG的两个引脚由同一个GPIO控制 初始时HART_CLK_CFG输出低电平 由RTS引脚控制调制/解调。当RTS处于高电平时,为解调(输入);否则为调…...

Go语言异步下载视频

异步下载mp4视频列表 下面是一个简单的Go语言示例,用于异步下载视频。我们将使用goroutines来实现异步下载,并使用sync.WaitGroup来等待所有下载任务完成。此示例依赖于net/http包来执行HTTP请求。 package mainimport ("fmt""io"…...

前缀树(字典树/Trie) -----Java实现

目录 一.前缀树 1.什么是前缀树 2.前缀树的举例 二.前缀树的实现 1.前缀树的数据结构 1.插入字符串 2.查找字符串 3.查找前缀 三.词典中最长的单词 1.题目描述 2.问题分析 3.代码实现 一.前缀树 1.什么是前缀树 字典树(Trie树)是一种树形…...

​申请专利需要具备什么条件

​申请专利需要具备什么条件 在我国,如果创造出来了新的发明都可以申请专利权,一旦申请成功之后,自己的发明就受到了法律的保护,任何人不得以违法的手段进行侵犯。那么申请专利需要具备什么条件?今天律赢时代网就为大家…...

【C++】一篇带你搞懂C++“引用”

前言在C语言的学习中,并没有引用这个概念,但是在C中,加入了引用这个概念,说明引用也是很重要的,但是我们怎么理解引用呢?我是这么理解的,例如在水浒传中,108个英雄好汉都是自己的外号…...

蓝桥杯刷题冲刺 | 倒计时19天

作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录1.抓住那头牛2.排列序数1.抓住那头牛 题目 链接: 抓住那头牛 - C语言网 (dotcpp.com…...

Java每日一练(20230321)

目录 1. 出现次数最多的字符 🌟 2. 最后一个单词的长度 🌟 3. 两数之和 🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 出现次数最多的字符并…...

【三维几何学习】从零开始网格上的深度学习-3:Transformer篇(Pytorch)

本文参加新星计划人工智能(Pytorch)赛道:https://bbs.csdn.net/topics/613989052 从零开始网格上的深度学习-3:Transformer篇引言一、概述二、核心代码2.1 位置编码2.2 网络框架三、基于Transformer的网格分类3.1 分类结果3.2 全部代码引言 本文主要内容如下&#…...

一、基础算法3:二分 模板题+算法模板(数的范围,数的三次方根)

文章目录算法模板整数二分算法模板浮点数二分算法模板模板题数的范围原题链接题目题解数的三次方根原题链接题目题解算法模板 整数二分算法模板 bool check(int x) {/* ... */} // 检查x是否满足某种性质// 区间[l, r]被划分成[l, mid]和[mid 1, r]时使用: int b…...

Spring 源码解析 - Bean创建过程 以及 解决循环依赖

一、Spring Bean创建过程以及循环依赖 上篇文章对 Spring Bean资源的加载注册过程进行了源码梳理和解析,我们可以得到结论,资源文件中的 bean 定义信息,被组装成了 BeanDefinition 存放进了 beanDefinitionMap 容器中,那 Bean 是…...

移除元素(双指针)

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的…...

76.qt qml-QianWindow开源炫酷界面框架(支持白色暗黑渐变自定义控件均以适配)

界面介绍界面支持: 透明 白色 黑色 渐变 单色 静态图 动态图侧边栏支持:抽屉、带折叠、多模式场景控件已集成: 暗黑风格 高亮风格、并附带个人自定义控件及开源demo白色场景如下所示:单色暗黑风格如下所示:用户自定义皮肤如下所示:皮肤预览如下所示:b站入口:https://www.bilibi…...

Python生日蛋糕

目录 前言 底盘 蛋糕 蜡烛 祝福 前言 Hello,小伙伴们晚上好吖!前两天博主满20岁啦(要开始奔三辽呜呜呜),这几天收到了不少小伙伴们的祝福,浪漫的小博主想送给大家一份不一样的生日蛋糕&#xff0c…...

QT 如何提高 Qt Creator 的编译速度

如何提高编译速度,貌似是一个老生常谈的话题。对于Qter而言,如何提高QT Creator 的编辑速度是一直都是大家所期盼的。本文也是查阅了各路大神的方法后整理出来的,希望对各位有所帮助。 1、在*.pro文件添加预编译机制 QT官方给出的示例&…...

STM32之震动传感器、继电器介绍及实战

目录 一、震动传感器介绍及实战 二、编程代码实现 1、gpio.c---------初始化GPIO口引脚函数 2、调用中断服务函数 3、中断服务函数 4、中断服务回调函数 5、把上述的中断服务回调函数,放入main主函数里 6、结果演示 三、继电器介绍及实战 一、震动传感器介…...

RK3588平台开发系列讲解(显示篇)RK3588 平台 的DP介绍

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、功能特性二、 DP 输⼊三、DP 输出四、 代码路径沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍 RK3588 平台 DP 的使⽤与调试⽅法。 一、功能特性 RK3588 的 DP ⽀持 1.4a 版本的 DP 协议,最…...

【Java】i++和++i的实现原理

文章目录 i++案例反编译分析扩展 x = x++我们接下来从字节码层面分析: 不了解字节码的可以参考这篇:【精通JVM】字节码指令全解 i++案例 package org.example;public class Main {public static void main...

第十四届蓝桥杯三月真题刷题训练——第 18 天

目录 第 1 题:排列字母 问题描述 运行限制 代码: 第 2 题:GCD_数论 问题描述 输入格式 输出格式 样例输入 样例输出 评测用例规模与约定 运行限制 第 3 题:选数异或 第 4 题:背包与魔法 第 1 题&#x…...

软件测试拿了几个20K offer,分享一波面经

1、你的测试职业发展是什么? 测试经验越多,测试能力越高。所以我的职业发展是需要时间积累的,一步步向着高级测试工程师奔去。而且我也有初步的职业规划,前3年积累测试经验,按如何做好测试工程师的要点去要求自己,不断…...

spring2

1.Spring配置数据源1.1 数据源(连接池)的作用 数据源(连接池)是提高程序性能如出现的事先实例化数据源,初始化部分连接资源使用连接资源时从数据源中获取使用完毕后将连接资源归还给数据源常见的数据源(连接池):DBCP、C3P0、BoneC…...

【Linux】网络编程套接字(中)

🎇Linux: 博客主页:一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看见坚持…...

手撕数据结构—队列

队列队列的话只允许在一端插入,在另外一端删除。插入数据的那一段叫做队尾,出数据的那一段叫做队头(从尾巴插入)。因此的话队列是先进先出的。入的顺序与出的顺序的话是一样的。这个与栈是不一样的,因为栈的话就是说如…...

gdb调试工具和makemakefile工具

gdb调试工具和make/makefile工具 文章目录gdb调试工具和make/makefile工具一、gdb调试工具1.debug/release2.使用二、make/makefile1.什么是make/makefile2.编写一、gdb调试工具 1.debug/release 程序有两种默认的发布方式debug和release。release是无法进行调试的。Linux中g…...

【进阶数据结构】平衡搜索二叉树 —— AVL树

🌈感谢阅读East-sunrise学习分享——[进阶数据结构]AVL树 博主水平有限,如有差错,欢迎斧正🙏感谢有你 码字不易,若有收获,期待你的点赞关注💙我们一起进步🚀 🌈我们上一篇…...

ROS使用(5)action学习

action消息的构建 首先进行功能包的创建 mkdir -p ros2_ws/src cd ros2_ws/src ros2 pkg create action_tutorials_interfaces action消息的类型 # Request --- # Result --- # Feedback 动作定义由三个消息定义组成,以---分隔。 从动作客户机向动作服务器发送…...