首页 > 其他分享 >volatile 关键字的工作机制

volatile 关键字的工作机制

时间:2024-07-24 22:32:38浏览次数:12  
标签:缓存 关键字 屏障 volatile 内存 机制 排序 处理器

Author: ACatSmiling

Since: 2024-07-24

volatile 关键字是 Java 编程语言中的一个重要工具,用于控制变量在多线程环境中的可见性和有序性。

前置知识

指令重排序

指令重排序(Instruction Reordering):是现代处理器和编译器优化技术的一部分,旨在提高程序执行效率。通过改变指令的执行顺序,可以更好地利用处理器流水线和缓存,从而提升性能。然而,在多线程环境中,指令重排序可能引发线程安全问题,因为它可能改变程序的预期执行顺序。

指令重排序的类型:

  1. 编译器重排序(Compiler Reordering):编译器在生成机器代码时,可能会根据依赖关系和优化策略对代码指令进行重排序。
  2. 处理器重排序(Processor Reordering):处理器在执行指令时,为了提高流水线效率和缓存利用率,可能会对指令进行重排序。处理器有专门的机制,如乱序执行(Out-of-Order Execution)和内存模型(Memory Model),来管理指令的执行顺序。

内存屏障

内存屏障(Memory Barrier)有时也称为内存栅栏(Memory Fence),是一种用于控制处理器和编译器在多线程环境中指令执行顺序的机制。内存屏障通过防止某些类型的指令重排序,确保特定的内存操作在程序中按照预期的顺序执行。

内存屏障的作用:

  1. 防止指令重排序:内存屏障强制指令在特定的顺序执行,防止编译器或处理器对其进行重排序。
  2. 确保内存可见性:内存屏障确保某些内存操作的结果对其他线程立即可见。特别是在多处理器环境中,内存屏障可以强制处理器将缓存的数据刷新到主内存,或者从主内存重新读取数据。

内存屏障主要有以下几种类型:

  1. Load Barrier(读屏障):确保屏障之前的所有读操作在屏障之后的读操作之前完成。
  2. Store Barrier(写屏障):确保屏障之前的所有写操作在屏障之后的写操作之前完成。
  3. Full Barrier(全屏障):同时具备读屏障和写屏障的功能,确保屏障之前的所有读写操作在屏障之后的读写操作之前完成。

在 Java 中,内存屏障主要通过 volatile 关键字和 synchronized 关键字得以应用:

  • volatile 关键字
    1. 读 volatile 变量时,会插入读屏障:确保在屏障之后的所有读操作都能看到 volatile 变量的最新值。
    2. 写 volatile 变量时,会插入写屏障:确保在屏障之前的所有写操作都已经完成并对其他线程可见。
  • synchronized 关键字:
    1. 进入 synchronized 块时,会插入读屏障:确保在进入临界区之前,所有的读操作都能看到最新的值。
    2. 退出 synchronized 块时,会插入写屏障:确保在退出临界区之前,所有的写操作都已经完成并对其他线程可见。

CPU 缓存一致性协议

CPU 缓存一致性协议(Cache Coherence Protocol)是用于确保在多处理器系统中,各个处理器的缓存内容保持一致的机制。当多个处理器共享同一块内存时,它们可能会在各自的缓存中存储该内存的副本。为了确保这些副本始终一致,缓存一致性协议被引入。

缓存一致性问题:在多处理器系统中,如果一个处理器修改了某个内存位置的值,而其他处理器缓存中仍然保留旧值,则会导致数据不一致的问题。这种情况在并发编程中非常常见,解决这一问题需要缓存一致性协议。

常见的缓存一致性协议

缓存一致性协议是多处理器系统中确保数据一致性的关键机制。通过定义缓存行的多种状态和相应的转换规则,缓存一致性协议有效地解决了数据不一致的问题。常见的协议包括 MSI、MESI、MOESI 和 MESIF,它们在不同的应用场景中各有优劣。

MSI 协议:MSI 是最简单的一种缓存一致性协议,它的名字来源于三种缓存行状态:

  • Modified(修改):缓存行的数据已被修改,且数据只在当前缓存中是最新的,主内存中的数据已过期。
  • Shared(共享):缓存行的数据可能被多个缓存共享,且数据与主内存中的一致。
  • Invalid(无效):缓存行的数据无效,需要从主内存或其他缓存获取最新数据。

MESI 协议:MESI 协议是 MSI 协议的扩展,多了一种状态:

  • Exclusive(独占):缓存行的数据是最新的,且只有当前缓存持有这个数据,主内存中的数据也是最新的。

MOESI 协议:MOESI 协议是在 MESI 协议基础上再扩展了一种状态:

  • Owner(拥有者):缓存行的数据是最新的,且数据可能被其他缓存共享,但当前缓存是数据的拥有者,负责向其他缓存提供数据。

MESIF 协议:MESIF 协议是英特尔的一种缓存一致性协议,增加了一种状态:

  • Forward(转发者):类似于 Shared 状态,但在多个缓存行都处于 Shared 状态时,Forward 状态的缓存行负责向其他请求者提供数据,减少了对主内存的访问。

缓存一致性协议的工作机制

缓存一致性协议通常采用以下两种机制来保证缓存的一致性:

  1. 总线嗅探(Bus Snooping):每个处理器的缓存控制器都会监听(嗅探)总线上其他处理器的读写操作,如果发现某个缓存行被修改,就会相应地更新或失效本地缓存中的数据。
  2. 目录协议(Directory Protocol):维护一个全局目录,记录每个缓存行在哪些处理器的缓存中。处理器的读写操作需要查询和更新这个全局目录,以确保一致性。

缓存一致性协议的实现示例

以下是一个简化的 MESI 协议工作示例。

假设有两个处理器 P1 和 P2,它们都缓存了内存地址 X 的数据:

  1. 初始状态:P1 和 P2 的缓存行状态都为 Shared。
  2. P1 修改 X:P1 将缓存行状态修改为 Modified,同时通过总线嗅探通知 P2 失效其缓存行,P2 的缓存行状态变为 Invalid。
  3. P2 读取 X:P2 发现其缓存行已失效,从 P1 或主内存获取最新数据,P1 的缓存行状态变为 Owner,P2 的缓存行状态变为 Shared。

volatile 的作用

volatile 主要有两个作用:

  1. 保证可见性当一个线程修改了 volatile 变量的值,新值会被立即刷新到主内存中,其他线程读取该变量时会直接从主内存中读取最新的值。
    • 在没有 volatile 的情况下,一个线程对变量的修改可能不会立即被其他线程看到,因为每个线程可能会在自己的工作内存(缓存)中操作变量的副本。
    • 使用 volatile 关键字时,任何对该变量的写操作都会立即刷新到主内存,并且任何读操作都会直接从主内存中读取。这样就确保了变量的最新值对所有线程可见。
  2. 禁止指令重排序对 volatile 变量的读写操作不会被编译器和处理器重排序,这保证了操作的有序性。
    • volatile 禁止了指令重排序优化。通常,编译器和处理器为了提高性能,可能会对指令进行重排序,但这种重排序会带来线程安全问题。
    • 使用 volatile 后,编译器和处理器会在读写 volatile 变量时插入内存屏障(Memory Barrier),确保在内存屏障前的操作不会被重排序到屏障之后,反之亦然。

volatile 无法保证原子性。

volatile 的工作原理

  1. 内存屏障(Memory Barrier)
    • 在写入 volatile 变量时,会插入写屏障(Store Barrier),确保在屏障之前的所有写操作都被刷新到主内存。
    • 在读取 volatile 变量时,会插入读屏障(Load Barrier),确保在屏障之后的所有读操作都从主内存中读取最新的值。
    • 三句话说明:
      1. volatile 写之前的操作,都禁止重排序到 volatile 之后。
      2. volatile 读之后的操作,都禁止重排序到 volatile 之前。
      3. volatile 写之后的 volatile 读,禁止重排序。
  2. CPU 缓存一致性协议:volatile 变量的写操作会触发缓存一致性协议,强制其他处理器的缓存行失效,从而确保所有处理器都能看到变量的最新值。

volatile 的应用场景

状态标志:适用于简单的状态标志或开关,例如一个布尔值,用于控制线程是否继续运行。

private volatile boolean running = true;

public void stop() {
    running = false;
}

public void run() {
    while (running) {
        // 执行任务
    }
}

双重检查锁定(Double-Checked Locking):用于实现线程安全的单例模式。

public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
	}
}

原文链接

https://github.com/ACatSmiling/zero-to-zero/blob/main/JavaLanguage/java-util-concurrent.md

标签:缓存,关键字,屏障,volatile,内存,机制,排序,处理器
From: https://www.cnblogs.com/acatsmiling/p/18321893

相关文章

  • JVM 内存结构、垃圾回收机制与并发容器
    目录一、JVM内存结构 1.程序计数器(ProgramCounterRegister): 2.Java虚拟机栈(JVMStack): 3.本地方法栈(NativeMethodStack): 4.堆(Heap): 5.方法区(MethodArea):二、垃圾回收机制 1.标记-清除算法: 2.复制算法: 3.标记-整理算法: 4.分代收集:三、并发容器......
  • 一文弄懂JVM类加载器与双亲委派机制
    类的加载器完成类的加载环节中的装载阶段的工作(通过一个类的全限定名来获取该类的二进制字节流,且这个动作在虚拟机**外部实现**,即开发者可以决定如何去获取所需的类),且**不会影响后续的链接和初始化阶段,但类的加载器的存在使得类不会卸载**。类的加载器的意义:加载器的意义......
  • 游戏中的组队匹配机制是如何设计?
    在游戏中,组队匹配机制是一种常见的玩法,它允许玩家们通过组队来增强游戏体验。这种机制的设计需要考虑到各种因素,包括玩家的技能水平、游戏难度、队伍规模等,接下来探讨这种机制的设计过程。1.玩家能力评估首先,设计者需要了解每个玩家的能力,这包括他们的技能水平、角色等级、装备......
  • python之名称空间和作用域(关键字:global和nonlocal的使用)
    文章目录前言1、名称空间和作用域1.1引言1.2名称空间1.2.1内置名称空间1.2.2全局名称空间1.2.3局部名称空间1.2.4名称空间的产生和销毁顺序1.3作用域1.3.1全局作用域1.3.2局部作用域1.3.3名字的查找顺序1.4关键字:global1.5关键字:nonlocal前言本篇文章......
  • 如何使用 Polars scan_parquet 扫描 parquet 中的关键字列表
    我有一个包含类别元数据的镶木地板文件。我想使用极地中的scan_parquet扫描此镶木地板文件,如下所示:filtered_df=(pl.scan_parquet(parquet_file).filter(pl.col("CATEGORIES").str.contains("people",literal=True)|pl.col("CATEGORIES")......
  • Java之this关键字详解
    this关键字在类中的普通成员方法中,可以使用this关键字,其表示调用当前方法的对象引用,即哪个对象调用该方法,this就代表哪一个对象。this关键字用法:对成员变量和局部变量进行区分固定格式:this.数据成员;调用类中的成员方法固定格式:this.成员方法(实际参数列表);调用......
  • 8. 深浅拷贝、可变与不可变、垃圾回收机制、字符编码、文件操作
    1.可变和不可变数据类型1.1概念可变数据类型:当指定值被修改时内存空间地址不变不可变数据类型:当指定值被修改时内存空间地址发生改变1.2常见类型的代码实现可变类型:list  dict set不可变类型:strint floatbooltuple(1)整数  不可变a=1print(id(a))#25......
  • Python贝叶斯、transformer自注意力机制self-attention个性化推荐模型预测课程平台学
    全文链接:https://tecdat.cn/?p=37090原文出处:拓端数据部落公众号 分析师:KungFu近年来,在线课程凭借便捷的网络变得越来越流行。为了有更好的用户体验,在线课程平台想要给用户推荐他们所感兴趣的课程,以便增大点击率和用户黏性。解决方案任务/目标根据学生所选的历史课程,预测出......
  • Python中的global关键字是如何工作的?
    在Python中,global关键字扮演着特殊而重要的角色,它主要用于在函数内部声明全局变量。理解global关键字的工作原理,首先需要明确Python中变量作用域的概念,以及为什么需要global关键字。下面,我将详细解释global关键字的工作机制,包括其作用、使用场景、注意事项,以及它在Python编......
  • 创新认证:坚持创新之路,完善创新体制机制
    在当今这个日新月异的时代,创新已成为推动社会进步与经济发展的核心动力。随着全球竞争的加剧和技术的飞速迭代,如何有效识别、鼓励和保护创新成果,成为了社会各界关注的焦点。在此背景下,创新认证作为一种新兴机制应运而生,它不仅是对创新能力的官方认可,更是激发创新潜能、促进创......