Java内存模型介绍

文章目录

  • Java内存模型
    • 前言
    • Java内存模型基本介绍
    • 指令重排相关概念
    • 主存和本地内存相关介绍
    • JMM中的8种同步规则和8种同步操作
    • happens-before 原则
    • 内存屏障
    • 总结

Java内存模型

前言

本文主要介绍一下JMM中的一些常见概念,通过本文让你能够快速的对JMM有一个大致的了解

Java内存模型基本介绍

  • Java内存模型是什么

    Java内存模型一般简称 JMM(Java Memeory Model),是为了规范 Java 程序多线程之间共享数据的访问规则而定义的。它主要关注程序中的变量在内存中的可见性、顺序性和操作原子性等方面的问题。Java内存模型定义了一系列的规则和保证,用于确保多线程程序中的共享变量能够正确、可靠地被访问和修改。

    温馨提示:一定不要和 JVM内存结构JVM内存模型Java内存模型三个概念给搞混了,看网上好多人直接认为 JVM 内存模型就算 JMM,三者之间的区别:

    • JVM内存结构:JVM内存结构也称 JVM内存区域、Java内存区域,是指 JVM 的内存组成,其中包括虚拟机栈、堆、本地方法栈、元空间(方法区)、程序计数器、直接内存
    • JVM内存模型:是比 JVM内存结构 更大的一个概念,它不经包含了JVM内存结构,还包括了JVM内存工作方式
    • Java内存模型:是针对多线程编程中共享数据的可见性、指定重排等问题而提供的一套规范,已解决多线程之间内存的一致性问题,JMM决定了一个线程堆共享变量的写入合适对另一个线程可见

    总结:JVM内存结构是JVM内存模型的子集,JVM内存模型侧重概念,Java内存模型侧重规范

    image-20230924100233315

    image-20230924100106815

  • 什么需要JMM

    因为多线程开发中会存在很多问题,比如共享数据的可见性问题、指令重排等问题,为了解决这些问题,就需要制定一些规范,也就是所谓的内存模型。一般来说,编程语言也可以直接复用操作系统层面的内存模型。不过,不同的操作系统内存模型不同。如果直接复用操作系统层面的内存模型,就可能会导致同样一套代码换了一个操作系统就无法执行了。Java的早期创建者为了确保 Java程序的可移植性,就直接设计了一套专属于 Java 的内存模型(规范),Java开发者只需要遵循这一套,就可以编写正确、可靠且高效的多线程程序,避免常见的并发问题,确保数据的一致性和线程安全性。

    总结:为了解决多线程种数据的一致性问题、指令重排问题,Java提供了一套规范也就是 JMM,通过这套规范,Java开发者在不需要了解底层原理就能很简单(直接使用并发相关的一些关键字和类)解决Java多线程开发种的一些问题。

  • JMM的作用

    JMM 说白了就是定义了一些规范来解决并发编程中的常见问题,开发者可以利用这些规范更方便地开发多线程程序。对于 Java 开发者说,你不需要了解底层原理,直接使用并发相关的一些关键字和类(比如 volatilesynchronized、各种 Lock)即可开发出并发安全的程序

    总结:解决多线程开发种遇到的指令重排、可见性问题

指令重排相关概念

  • 什么是指令重排

    指令重排(Instruction Reordering)是指编译器处理器在执行程序时,为了优化性能而改变原始指令序列的顺序。这种重排可能会导致代码的执行顺序与源代码中编写的顺序不一致。

  • 为什么需要指令重排

    指令重排的出现是为了提高程序的执行效率和性能。处理器或编译器会尽量利用现代计算机体系结构的特性,如流水线执行、乱序执行、寄存器重命名等技术进行优化。通过重排指令,可以更好地利用处理器的资源减少潜在的数据依赖和等待时间,从而加快程序的执行速度

  • 导致指令重排的原因有哪些

    • 数据依赖性:某些指令的执行依赖于前面指令的结果。如果处理器或编译器认为这些依赖关系不会产生冲突,并且执行后续指令不受影响,就可以对指令进行重排。例如,A依赖C,B依赖C,A和B互不依赖,就会重排A和B,但是C和A、B是无法重排
    • 内存屏障(Memory Barriers):内存屏障是一种同步操作,用于控制内存访问的顺序。当处理器或编译器遇到内存屏障时,会按照屏障的指令序列来确保指令重排不会破坏程序的语义一致性。但是,如果没有内存屏障或者内存屏障的使用不正确,可能会导致指令重排。
    • 编译器优化:编译器在生成目标代码时,会进行各种优化以提高执行效率。其中包括指令重排。编译器根据机器体系结构的特性和优化策略,尽可能地改变指令的执行顺序,以减少潜在的数据依赖和等待时间。这种优化可能会引起指令重排。
    • 处理器的乱序执行:现代处理器通常具有乱序执行(Out-of-Order Execution)的能力。它们可以对指令进行重排,以最大程度地利用处理器资源并提高执行效率。在乱序执行期间,处理器会根据依赖关系和可用资源来决定指令的实际执行顺序。

    总结:导致指令重排序的原因无非分为三大类

    1. 编译器导致的指令重排,比如:编译器优化
    2. 处理器导致的指令重排,比如:数据依赖性重排、处理器的乱序执行
    3. 内存系统导致的指令重排,比如:内存屏障

Java 源代码会经历 编译器优化重排 → 指令并行重排 → 内存系统重排 的过程,最终才变成操作系统可执行的指令序列。

注意指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致 ,所以在多线程下,指令重排序可能会导致一些问题。

  • 指令重排会带来哪些问题
    • 可见性问题:指令重排可能导致多线程环境下的可见性问题。如果在重排后,一个线程修改了某个共享变量的值,另一个线程可能无法立即看到这个修改,从而导致出现数据不一致的情况。
    • 有序性问题:指令重排可能破坏原本程序中的顺序关系,导致结果与预期不符。例如,如果两条指令之间有依赖关系且被重排了,可能会产生错误的结果。
    • 并发安全问题:指令重排可能导致并发安全问题,如竞态条件(Race Condition)和死锁(Deadlock)。当多个线程同时对共享资源进行读写操作时,由于重排可能改变了指令的执行顺序,可能会导致意料之外的结果或死锁的发生。
  • 如何解决重排带来的问题
    • 使用合适的同步机制来确保可见性和有序性,如使用volatile关键字、synchronized关键字、Lock等。
    • 合理使用内存屏障(Memory Barriers)来控制重排序行为。
    • 编写线程安全的代码,避免竞态条件和其他并发问题的发生。
    • 进行测试和调试,确保程序在多线程环境下的正确性。

主存和本地内存相关介绍

  • 什么是主存

    主存在 JMM 中是一个逻辑上的概念1,它定义了多线程程序中共享变量的可见性和一致性规则,要求所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)

    注意:不要将 JMM 中的 ”主存“ 这个逻辑概念和平常所说的”主存“这个物理概念搞混了,物理概念上的主存是指RAM(随机存取器),也称”内存“

  • 什么是本地内存

    每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存(也称工作内存),无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。

    注意:线程对于共享变量的读写都是发生在工作内存中的

  • JMM为什么要划分主存和内存

      在 JDK1.2之前 ,Java虚拟机(JVM)使用的是主存模型。在主存模型中,所有线程都直接读写主存中的共享变量,这样做会产生一些问题,例如,由于缓存一致性协议2的存在,不同的处理器可能会有自己的缓存,导致线程间无法及时看到对共享变量的修改,导致出现数据一致性问题。此外,编译器也可能对代码进行指令重排序,进一步破坏多线程之间的一致性。

      在 JDK1.2时,Java引入了本地内存这个概念概念,本地内存位于每个线程的工作内存中,用来存储线程私有的数据。当一个线程访问共享变量时,首先会将共享变量从主存复制到本地内存中进行操作,然后再将修改后的值刷新回主存。借助本地内存,可以解决缓存一致性问题。当一个线程修改了本地内存中的共享变量后,其他线程可以通过主存来感知到这个变化。此外,通过限制对本地内存的访问,可以避免编译器过度优化和指令重排序,从而保证多线程程序的正确执行。

    添加了本地内存后读写共享变量的具体流程:

    1. 线程 1 想要修改共享变量,判断本地内存中的共享变量是否存在
      1. 如果共享变量不存在就直接从主存中刷新出来
      2. 如果共享变量存在,利用缓存一致性协议判断共享变量的数据是否过期了
        1. 共享变量过期了,则刷新共享变量(将主存中的共享变量数据拷贝到本地内存中),然后修改
        2. 共享变量未过期,直接修改
      3. 线程1修改完后将共享变量的数据保存到主存中
    2. 线程 2 想要读取共享变量,判断本地内存中的共享变量是否存在
      1. 如果共享变量不存在就直接从主存中刷新出来
      2. 如果共享变量存在,利用缓存一致性协议判断共享变量的数据是否过期了
        1. 共享变量过期了,则刷新共享变量(将主存中的共享变量数据拷贝到本地内存中),然后读取
        2. 共享变量未过期,直接读取

JMM中的8种同步规则和8种同步操作

前面介绍了 JMM 是什么?说到 JMM 就是一套规范,而这套规范的核心就算 8种同步规则 和 8种同步操作,通过遵循这8种同步规则和8种同步操作,我们就能够很大程度上避免多线程开发中出现数据一致性问题和可见性问题。本小节我们将介绍以下这8种同步规则和8种同步操作

  • 8种同步规则

    • 不允许 read 和 load、store 和 write 操作之一单独出现。即使用了 read 必须 load,使用了 store 必须 write
    • 不允许线程丢弃他最近的 assign 操作,即工作变量的数据改变了之后,必须告知主存
    • 不允许一个线程将没有 assign 的数据从工作内存同步回主内存
    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施 use、store操作之前,必须经过 assign 和 load 操作
    • 一个变量同一时间只有一个线程能对其进行 lock。多次 lock 后,必须执行相同次数的 unlock 才能解锁
    • 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新 load 或 assign 操作初始化变量的值
    • 如果一个变量没有被 lock,就不能对其进行 unlock 操作。也不能 unlock 一个被其他线程锁住的变量
    • 对一个变量进行 unlock 操作之前,必须把此变量同步回主内存
  • 8种同步操作

    • 锁定(lock): 作用于主内存中的变量,将他标记为一个线程独享变量。

    • 解锁(unlock): 作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定。

    • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。

    • load(载入):把 read 操作从主内存中得到的变量值放入工作内存的变量的副本中。

    • use(使用):把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令。

    • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

    • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的 write 操作使用。

    • write(写入):作用于主内存的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中。

Java官方基于这8种同步规则和8种同步操作进行内部实现,我们平常使用的如:synchronized关键字、volatile修饰符、Lock等都是Java官方提供的内部实现,他们都遵循了这两个8。

那么是不是我们这些 API 调用工程师是不是就只需要使用这些提供好的内部实现即可,不用理会这两个8?非也非也,仅仅依赖于内部实现是不够的。作为开发人员,了解并遵守JMM的同步规则以及合理地使用同步操作是非常重要的,以确保编写出具有正确并发行为的多线程程序。通过遵循这些规则和操作,可以避免潜在的并发问题,例如数据竞争、死锁、活锁等。此外,合理地使用同步机制还能提高程序的性能和效率。

happens-before 原则

  • happens-before原则是什么

    happens-before 原则是用于描述多线程程序中操作之间的时间顺序关系的一套规则,具体可以分为8种不同的规则。

  • happens-before原则的包含的八种规则

    • 程序顺序规则(Program Order Rule):在同一个线程中,按照程序代码的先后顺序执行的操作,前一个操作的结果对后续操作可见。

    • 锁定规则(Lock Rule):一个释放锁的操作(unlock)happens-before 后续相同锁的获取锁操作(lock)。这意味着,在解锁之前对共享变量所做的修改对于后续获取该锁的线程是可见的。

    • volatile变量规则(Volatile Variable Rule):对一个volatile变量的写入操作happens-before 后续对该变量的读取操作。这保证了对volatile变量的修改对所有线程是可见的。

    • 传递性规则(Transitive Rule):如果操作A happens-before 操作B,且操作B happens-before 操作C,则操作A happens-before 操作C。这个规则可以推导出更复杂的happens-before关系。

    • 线程启动规则(Thread Start Rule):一个线程的启动操作happens-before 该线程中的任何操作。

    • 线程终止规则(Thread Termination Rule):一个线程中的任何操作happens-before 其他线程检测到该线程已经终止的操作。

    • 线程中断规则(Thread Interruption Rule):对于任意线程,调用interrupt()方法的操作happens-before 被中断线程检测到中断事件的操作。

    • 对象终结规则(Finalizer Rule):一个对象的构造函数执行happens-before 该对象的finalize()方法。

  • happens-before原则的作用是什么

    1. 保证可见性:根据happens-before原则,如果一个操作A happens-before另一个操作B,那么操作A的结果对操作B来说是可见的。这意味着,通过正确使用同步机制(如volatile、synchronized、Lock等),可以确保对共享变量的修改对其他线程是可见的,从而避免了数据不一致性和竞态条件。
    2. 确保顺序性:happens-before原则定义了操作之间的时间顺序关系,可以确保按照预期的顺序执行操作。例如,一个线程的启动操作happens-before该线程中的任何操作,这样就可以确保子线程中的操作在子线程开始运行之前完成。
    3. 避免编译优化问题:happens-before原则还能够防止编译器和处理器对指令进行重排序,以保证指令的执行顺序符合预期。这样可以避免由于编译优化导致的程序行为异常。
    4. 提供并发安全性:通过happens-before原则,程序员可以使用同步机制来创建临界区,保护共享资源的访问和修改。这样可以避免多线程并发访问共享资源时出现不一致的情况,提供并发安全性。

    主要作用是确保操作的顺序性,当满足的操作的顺序性时,自然可以满足可见性和指令重排等问题

  • happens-before原则和JMM的关系是什么

    JMM是对于多线程开发的一套规范,happens-before原则是JMM呈现给程序员的抽象视图,让程序员通过合理地设置操作之间的 happens-before 关系,来确保代码在多线程环境下的正确性,从而不必要过度关注底层的实现。

    happens-before 与 JMM 的关系

内存屏障

  • 什么是内存屏障

    内存屏障(Memory Barrier),也称为内存栅栏,是一种硬件或软件指令,用于控制处理器和内存之间的操作顺序和可见性。

  • 为什么需要内存屏障

    在多线程并发执行的环境下,由于处理器和内存的优化机制,可能会对代码的执行顺序进行重排序,导致线程间的可见性和有序性问题。为了解决这些问题,内存屏障被引入。

  • JMM和内存屏障的关系

    内存屏障是实现 JMM 的一种手段。JMM 规定了编译器和处理器如何通过插入内存屏障来确保多线程间操作的顺序性和可见性。内存屏障可以控制指令重排序和数据在处理器缓存和主内存之间的同步,从而保证了线程间的互相可见性和正确的执行顺序。

    在 JMM 中,通过使用 volatile 关键字或锁机制(如 synchronizedLock)来实现内存屏障的效果。当一个线程通过 volatile 变量或锁来与主内存进行交互时,会自动插入适当的内存屏障,以确保变量的可见性和有序性。

  • 内存屏障的分类

    • 读-写屏障(Load-Store Barrier):确保在屏障之前的读操作不会被重排到屏障之后的写操作之后。即,在读-写屏障之前的读取操作必须先于屏障之后的写入操作完成。
    • 写-写屏障(Store-Store Barrier):确保在屏障之前的写操作不会被重排到屏障之后的写操作之前。即,在写-写屏障之前的写入操作必须先于屏障之后的写入操作完成。
  • 内存屏障的作用

    • 强制刷新缓存:内存屏障可以强制将处理器缓存中的数据刷新回主内存,保证共享变量的可见性。
    • 禁止指令重排序:内存屏障可以限制指令的重排序,确保指令按照程序的原有顺序执行,从而保证程序的正确性。
    • 控制内存访问顺序:内存屏障可以控制不同线程对内存的读写操作的顺序,保证操作的有序性。

总结

  1. JMM是多线程开发种的一套规范,这套规范能够很大程度避免因为指令重排、可见性问题导致的问题
  2. 指令重排是为了提高程序执行的效率,指令重排包括编译器重排、处理器重排、缓存系统重排,指令重排能过保障串行执行的语义化一致,但是不会保障并发执行时的语义化一致
  3. 为了解决可见性问题 JDK1.2 引入本地内存的概念(为什么引入本地内存后就能够解决可见性问题)

参考资料

  • JMM(Java 内存模型)详解 | JavaGuide(Java面试 + 学习指南)
  • 【并发编程的艺术】详解指令重排序与数据依赖-腾讯云开发者社区-腾讯云 (tencent.com)

  1. 逻辑概念:逻辑概念是指在抽象层面上对事物或概念进行定义、描述和理解的方式或观点。它不涉及具体的物理实体或实际操作,而是通过逻辑推理和概念抽象来表达和处理思想、关系、规则等 ↩︎

  2. 缓存一致性协议:指多处理器系统或多核系统中用于保持共享数据一致性的协议。在这样的系统中,每个处理器或核心都有自己的高速缓存,用于提高访问速度 ↩︎

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

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

相关文章

如何看待Unity新的收费模式?

🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…

在uniapp中使用 秋云ucharts图表,运行到小程序

步骤一:通过使用 HBuilderX 直接导入项目(uni_modules 版本) 步骤二:在uCharts官网 - 秋云uCharts跨平台图表库 演示中,先弄个demo试着运行, 步骤三:查看自己需要的配置, 下面是我的…

保姆级 Keras 实现 Faster R-CNN 十三 (训练)

保姆级 Keras 实现 Faster R-CNN 十三 训练 一. 将 Faster R-CNN 包装成一个类二. 修改模型结构1. 修改 input_reader 函数2. 增加 RoiLabelLayer 层 三. 损失函数1. 自定义损失函数2. 自定义精度评价函数 四. 模型编译五. 模型训练六. 预训练模型七. 保存模型与参数八. 代码下…

MySQL高可用

目录 MySQL高可用方案 1、MHA架构(单主) MHA的工作原理 MHA 架构的优点 MHA 架构的缺点 2、MHA架构的部署 1)关闭防火墙和selinux 2)分别修改master和slave1,slave2的主机名 3)修改master主库服务…

(09_22)【有奖体验】轻点鼠标,让古籍数字化“重生_

卷帙浩繁的古籍是古典文化的载体,珍贵的古籍往往很难轻易示人,数字化是解决古籍‘藏’与‘用’之间矛盾的最好方式,函数计算联合开发者宋杰开发“古籍识别“应用,希望更多开发者行动起来,用Serverless AI 让古籍“活”…

2023蓝帽杯南部赛区半决赛取证复现

首先嗷,仅代表个人评价一下就是说赛委会在出题的时候不严谨,我一度怀疑我的语文阅读能力有问题,但是呢,这次的取证题目虽然不是很难,但是有些地方我也是依旧没有找到,说了这么多,接下来&#xf…

Qt地铁智慧换乘系统浅学( 三 )最少路径和最少换乘实现

本算法全都基于广度优先 概念最短路径实现所用容器算法思路 最少换乘实现所需容器算法思路 成果展示代码实现判断是最短路径还是最少换乘最短路径代码实现最少换乘代码实现根据所得List画出线路 ui界面的维护(前提条件)界面初始化combox控件建立槽函数 概…

Java中的数组

1.数组的概念 数组概念: 数组就是用于存储数据的长度固定的容器,保证多个数据的数据类型要一致。 百度百科中对数组的定义: 所谓数组(array),就是相同数据类型的元素按一定顺序排列的集合,就是把有限个类型相同的变…

Redis核心数据结构实战与高性能解析

目录 一、安装Redis 二、Redis线程与高性能 2.1 Redis是单线程么? 2.2 Redis读写是单线程为何这么快? 2.3 Redis如何处理并发操作命令? 三、核心数据结构实战 3.1 字符串常用操作实战 SET 存入键值对 SETNX SETEX MSET 批量存入键…

华为云云耀云服务器L实例评测|华为云上安装kafka

文章目录 华为云云耀云服务器L实例评测|华为云上安装kafka一、kafka介绍二、华为云主机准备三、kafka安装1. 安装什么版本java2. 安装zookeeper服务3. 使用systemctl 管理启动ZooKeeper服务4. 修改kafka配置5. 使用systemctl 管理启动kafka服务6. 创建一个测试 topi…

Vue路由及Node.js环境搭建

目录 一.Vue路由 1.1 定义 1.2 应用领域 1.3 代码展示 二、Node.js 2.1 定义 2.2 特点 三.Node.js安装与配置 3.1.下载 3.2.安装 3.3.环境搭建 好啦今天到这了,希望帮到你!!! 一.Vue路由 1.1 定义 Vue路由是指使用Vue Router…

大数据-hadoop

1.hadoop介绍 1.1 起源 1.2 版本 1.3生产环境版本选择 Hadoop三大发行版本:Apache、Cloudera、Hortonworks Apache版本最原始的版本 Cloudera在大型互联网企业中用的较多 Hortonworks文档较好 1.4架构 hadoop由三个模块组成 分布式存储HDFS 分布式计算MapReduce 资源调度引擎Y…

单片机上软字库换32进制存储,空间占用少20%

在之前的单片机字库建立的推送中: https://blog.csdn.net/platform/article/details/130742775, 存储了GB2312字符集对应的软字库文件,在16*16的编码下总字库的507KB,后来把字体切换成了12*12,软字库缩减到了301KB。当然这里面对…

Android---底部弹窗之BottomSheetDialog

BottomSheetDialog 是Android开发中的一个弹出式对话框,它从屏幕底部弹出并覆盖部分主界面。 1. BottomSheetDialog的使用 // 参数2:设置BottomSheetDialog的主题样式;将背景设置为transparent,这样我们写的shape_bottom_sheet_…

20230918使用ffmpeg将mka的音频转为AAC编码以便PR2023来识别

20230918使用ffmpeg将mka的音频转为AAC编码以便PR2023来识别 2023/9/18 20:58 ffmpeg -i 1.mka -acodec aac 1.mp4 ffmpeg -i 1.mka -vn -c:a aac 2.aac ffmpeg -i 1.mka -vn -c:a aac 2.MP4 ffmpeg mka 转 aacmp4 https://avmedia.0voice.com/?id42526 用ffmpeg将mka格式转化…

华为云云耀云服务器L实例评测 | Docker 部署 Reids容器

文章目录 一、使用Docker部署的好处二、Docker 与 Kubernetes 对比三、云耀云服务器L实例 Docker 部署 Redis四、可视化工具连接Redis⛵小结 一、使用Docker部署的好处 Docker的好处在于:在不同实例上运行相同的容器 Docker的五大优点: 持续部署与测试…

AI绘图提示词Stable Diffusion Prompt 笔记

基础 提示词分为正向提示词(positive prompt)和反向提示词(negative prompt),用来告诉AI哪些需要,哪些不需要词缀的权重默认值都是1,从左到右依次减弱,权重会影响画面生成结果。AI …

Spring Boot集成Redis实现数据缓存

🌿欢迎来到@衍生星球的CSDN博文🌿 🍁本文主要学习Spring Boot集成Redis实现数据缓存 🍁 🌱我是衍生星球,一个从事集成开发的打工人🌱 ⭐️喜欢的朋友可以关注一下🫰🫰🫰,下次更新不迷路⭐️💠作为一名热衷于分享知识的程序员,我乐于在CSDN上与广大开发者…

C++标准模板库STL——list的使用及其模拟实现

1.list的介绍 list的文档介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个…

【C++】开源:单元测试框架gtest配置使用

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍单元测试框架gtest配置使用。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下&#xff…