• Java中的volatile关键字


    1. volatile概述

    Java中的volatile关键字用于声明一个变量为“易变”的,即可以被不同线程同时访问和修改。这个关键字的主要作用是确保变量的可见性和禁止指令重排序,但它不提供操作的原子性。以下是volatile关键字的详细解释:

    1. 可见性(Visibility)
      在多线程环境中,为了提高性能,每个线程可能会将共享变量的副本缓存在自己的内存区域(如寄存器或本地内存)中。因此,一个线程对这些共享变量的修改可能对其他线程不可见。声明一个变量为volatile后,它会告诉JVM,每次访问这个变量时都要从主内存中读取,每次修改后都要立即写回主内存。这样可以确保该变量对所有线程的可见性。

    2. 禁止指令重排序(Ordering)
      在Java内存模型中,为了优化性能,编译器和处理器可能会对指令进行重排序。但在某些情况下,重排序可能会破坏程序的正确性。当一个字段被声明为volatile后,JVM会确保对这个变量的读写操作不会和其他内存操作一起被重排序。

    3. 不保证原子性(Atomicity)
      虽然volatile可以确保单个读取和写入操作的原子性,但它并不保证更复杂的原子操作,比如递增操作(i++)。这是因为递增操作不是单一的原子操作,而是由读取、修改和写入多个步骤组成的。如果需要复杂的原子操作,应该使用java.util.concurrent.atomic包下的原子变量类,或者同步机制。

    • 使用场景,volatile适用于以下场景:

      • 确保变量的修改对所有线程立即可见。
      • 读取和写入是不依赖于当前值或者只有单一的线程修改变量的值。
      • 作为状态标志,指示发生了一个重要的一次性事件(如初始化完成)。
    • 总结
      总的来说,volatile关键字在多线程编程中是一个重要的工具,用于确保变量的可见性和防止指令重排序,从而提供线程安全的环境。然而,它的使用需要谨慎,因为它不解决所有的并发问题,特别是在涉及到复杂状态的原子性操作时。

    2. volatile原理

    volatile 关键字在Java中用于确保变量的可见性和防止指令重排序,但它不提供原子性保证。它的底层原理涉及到Java内存模型(Java Memory Model, JMM)和现代计算机硬件的工作方式。以下是 volatile 的主要底层机制:

    1. 内存可见性

    • 内存屏障(Memory Barriers)volatile 变量的读写操作会插入特定类型的内存屏障指令,这些指令帮助实现变量操作的可见性。
    • 写屏障(Write Barrier):当写入一个 volatile 变量时,在写操作之后会插入一个写屏障,这确保了写入操作之前的所有操作(不仅仅是对 volatile 变量的操作)都不会被重排序到写操作之后。
    • 读屏障(Read Barrier):当读取一个 volatile 变量时,在读操作之前会插入一个读屏障,这确保了读操作之后的所有操作(不仅仅是对 volatile 变量的操作)都不会被重排序到读操作之前。

    2. 防止指令重排序

    • 重排序:编译器和处理器为了优化程序性能,可能会改变指令的执行顺序。
    • 禁止重排序volatile 通过内存屏障阻止特定类型的指令重排序,确保程序的执行顺序与代码的书写顺序在某种程度上保持一致。

    3. 缓存一致性

    • 处理器缓存:现代处理器使用缓存来减少访问内存的时间。这可能导致多核处理器中运行的线程看到不一致的数据。
    • 缓存一致性协议:例如,Intel x86 架构下使用的 MESI 协议。通过这些协议,当一个 volatile 变量被修改后,其他处理器中的缓存副本会被标记为无效,迫使其他线程在接下来访问该变量时重新从主内存中读取。

    4. 直接从主内存访问

    • volatile 变量:普通变量的值可能被缓存在本地线程的堆栈中,这导致其他线程可能看不到最新的值。
    • volatile 变量:对 volatile 变量的读写操作总是直接在主内存中进行,确保了不同线程间对这个变量的操作总是可见的。

    总结

    volatile 关键字是并发编程中的一项重要机制,它通过内存屏障、禁止特定类型的指令重排序以及确保操作直接在主内存上进行来保证内存的可见性和有序性。然而,它不提供原子性操作,因此在某些情况下仍然需要通过其他手段(如使用 synchronized 关键字或 java.util.concurrent 包中的类)来确保线程安全。

    3. volatile使用方式

    volatile 关键字在Java中主要用于确保多线程环境下变量的可见性,防止指令重排序,但它并不提供操作的原子性。正确使用 volatile 的方式如下:

    适合使用场景

    • 状态标志:在多个线程间共享的状态标志,比如一个表示是否停止的标志。
    • 单次安全发布:当一个对象的初始化状态需要对其他线程立即可见时。
    • 独立观察:变量不依赖于对象的当前状态或者其他变量的状态。
    • 限制指令重排序:防止编译器对代码执行顺序进行优化重排,从而影响多线程中的数据一致性。

    不适合使用场景

    • 依赖状态的操作:如果变量的当前值依赖于其之前的值(例如计数器、累加器),则 volatile 不足以保证操作的原子性。
    • 锁定多个变量:如果一个操作涉及到多个变量,单独将它们声明为 volatile 不能保证操作的原子性。

    示例

    正确的使用方式

    public class MyRunnable implements Runnable {
        private volatile boolean running = true;
    
        public void run() {
            while (running) {
                // 执行任务
            }
        }
    
        public void stopRunning() {
            running = false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这个例子中,running 变量用来控制线程是否继续运行。由于它被声明为 volatile,因此保证了一个线程对 running 的修改对其他线程立即可见。

    不正确的使用方式

    public class Counter {
        private volatile int count = 0;
    
        public void increment() {
            count++;  // 非原子操作
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这个例子中,即使 count 被声明为 volatilecount++ 操作(读取-修改-写入)并不是原子的,所以这个代码在多线程环境下是不安全的。

    注意事项

    • 有限使用:尽管 volatile 在某些情况下是有用的,但它不应该被过度使用。过多地使用 volatile 可能会影响程序性能,因为每次访问 volatile 变量都需要从主内存进行,而不是从缓存读取。
    • 不保证原子性volatile 只能保证单个读或写操作的原子性。对于复合操作,需要使用额外的同步机制,如 synchronized 关键字或 java.util.concurrent.atomic 包中的类。
    • 与其他同步机制结合使用:在需要更复杂的同步策略时,volatile 可以与锁或其他并发工具结合使用。

    总之,volatile 是一个用于特定场景的轻量级同步机制,了解并正确使用它对编写可靠的多线程程序至关重要。

  • 相关阅读:
    生物素偶联二硒化钨WSe2 (Biotin-WSe2)|羟基修饰PEG化二硒化钨WSe2纳米颗粒 (OH-WSe2)齐岳
    深度学习实战07-卷积神经网络(Xception)实现动物识别
    并发调用deepseek API,构建多轮对话数据
    一三六、从零到一实现自动化部署
    influxdb2的使用
    队列题目:无法吃午餐的学生数量
    YOLOv8血细胞检测(15):微小目标检测的上下文增强和特征细化网络ContextAggregation
    vulhub中GitLab 远程命令执行漏洞复现(CVE-2021-22205)
    代码随想录算法训练营第五十八天|739. 每日温度、496.下一个更大元素 I
    【小算法】两个 vector,对其中一个排序,另一个位置对应变化
  • 原文地址:https://blog.csdn.net/m0_54187478/article/details/134515722