volatile关键字的作用和原理

本文详细介绍了Java中volatile关键字的作用和原理,包括保证可见性和禁止指令重排序,并探讨了volatile不能确保原子性的原因及其适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

volatile关键字的作用和原理

关键字作用

  • volatile 保证可见性

    一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

    • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

    • 禁止进行指令重排序。

  • volatile 不能确保原子性

    public class VolatileVisibility {
    
        public static volatile int i =0;
    
        public static void increase(){
            i++;
        }
    }

    读数据和操作数据是两次操作

volatile原理

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  • 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

  • 它会强制将对缓存的修改操作立即写入主存;

  • 如果是写操作,它会导致其他CPU中对应的缓存行无效。

应用场景

synchronized 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而 volatile 关键字在某些情况下性能要优于 synchronized ,但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile 必须具备以下 2 个条件:

  • 对变量的写操作不依赖于当前值

  • 该变量没有包含在具有其他变量的不变式中

具体应用情况

  • 状态标记量

    volatile boolean flag = false;
    
    while(!flag){
        doSomething();
    }
    
    public void setFlag() {
        flag = true;
    }
  • double check

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

参考

### Java 中 `volatile` 关键字的实现原理 #### 主内存与工作内存的关系 在 Java 的内存模型 (JMM) 中,所有的变量都存储在主内存中。每个线程都有自己的工作内存,当线程需要操作某个变量时,会从主内存中加载该变量的一个副本到线程的工作内存中进行操作[^3]。这种设计可能导致一个问题:如果多个线程同时操作同一个变量,可能会因为各线程之间的可见性有序性问题而导致数据不一致。 #### 可见性问题 如果没有使用 `volatile` 关键字,一个线程对共享变量的修改可能不会立即同步回主内存,其他线程看到的可能是旧值或未更新的值。这是因为线程的操作是在其工作内存中完成的,而不是直接作用于主内存。这就会引发所谓的 **可见性问题**[^5]。 #### 禁止指令重排序 除了可见性问题外,还存在 **指令重排序** 的风险。为了优化性能,现代 CPU JVM 都允许对指令顺序进行调整,只要这些调整不影响单线程内的逻辑即可。然而,在多线程环境下,这种重排序可能会破坏程序预期的行为。例如,某些初始化代码被提前执行,而依赖它的后续代码却滞后,从而导致错误的结果[^2]。 #### Volatile作用 `volatile` 关键字作用主要体现在两个方面: 1. **保证可见性** 当一个变量被声明为 `volatile` 后,任何对该变量的写入都会立刻刷新到主内存,并且每次读取这个变量前都需要重新从主内存获取最新值,而非使用本地缓存中的拷贝。这样就解决了多线程环境下的可见性问题[^5]。 2. **防止指令重排序** 使用了 `volatile` 的变量可以确保在其上的读写操作具有一定的顺序约束。具体来说,对于任意给定的 `volatile` 变量 V ,假设有两条语句 S1 S2: - 如果 S1 是对 V 的写操作,S2 是对 V 的读操作,则 JMM 将强制要求 S1 必须发生在 S2 之前; - 此外,即使没有显式的锁机制参与其中,也可以利用 Happens-Before 原则来保障跨线程间的正确通信行为[^1]。 以下是基于以上理论编写的一段简单示例代码展示如何运用 volatile 来解决实际开发过程中遇到的一些典型并发问题: ```java public class VolatileExample { private static volatile boolean flag = true; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while(flag){ // do something here... } System.out.println("Thread stopped."); }).start(); TimeUnit.SECONDS.sleep(2); flag = false; } } ``` 在这个例子当中, 我们通过设置标志位flag控制另一个线程停止循环运行状态. 而之所以能够成功是因为我们将此布尔型字段标记成了volatile类型. --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值