• 详解Volatile关键字


    一、概述
    volatile是Java中的关键字,用来修饰会被不同线程访问和修改的变量,它可以保证并发编程三大特征(原子性、可见性、有序性)中的可见性和有序性,不能保证原子性。

    二、作用
    1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
    2. 保证有序性:禁止指令重排序。

    (1)线程的可见性
    先通过例子看下:

    public class VolatileDemo {
        boolean show = true;
    
        public void updateShow() {
            this.show = false;
            System.out.println("修改show值为:" + this.show);
        }
    
        public static void main(String[] args) {
            VolatileDemo demo = new VolatileDemo();
            new Thread(() -> {
                while (demo.show) {
                }
                System.out.println(Thread.currentThread().getName() + "结束");
            }, "Thread1").start();
    
            new Thread(() -> {
                try {
                    Thread.sleep(2000);
                    demo.updateShow();
                } catch (InterruptedException e) {
                }
            }, "Thread2").start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    打印结果可知,虽然线程Thread2已经把show 修改为false了,但是线程Thread1没有读取到show 修改后的值,线程一直在运行。
    我们把show变量加上volatile:

        volatile  boolean show = true;
    
    • 1

    打印结果可知 ,Thread1结束,说明Thread1读取到了show 修改后的值。

    由此可知,加了volatile关键字修饰的变量,只要有一个线程将主内存中的变量值做了修改,其他线程都将马上收到通知,立即获得最新值。当写线程写一个volatile变量时,JMM会把该线程对应的本地工作内存中的共享变量值刷新到主内存。当读线程读一个volatile变量时,JMM会把该线程对应的本地工作内存置为无效,线程将到主内存中重新读取共享变量。

    volatile可见性的实现是借助了CPU的lock指令,lock指令在多核处理器下,可以将当前处理器的缓存行的数据写回到系统内存,同时使其他CPU里缓存了该内存地址的数据置为无效。通过在写volatile的机器指令前加上lock前缀,使写volatile具有以下两个原则:
    1.写volatile时处理器会将缓存写回到主内存。
    2.一个处理器的缓存写回到内存,会导致其他处理器的缓存失效。

    (2)保证有序性(禁止指令重排序)
    计算机在执行程序时,为了提高计算性能,编译器和处理器常常会对指令进行重排序,一般分为如下3种:
    源代码 ——> 编译器优化的重排 ——> 指令并行的重排 ——>内存系统的重排 ——> 最终执行的指令

    有序性的实现原理:
    volatile有序性的保证就是通过禁止指令重排序来实现的。指令重排序包括编译器和处理器重排序,JMM会分别限制这两种指令重排序。

    volatile通过加内存屏障来实现禁止指令重排序。JMM为volatile加内存屏障有以下4种情况:

    1)在每个volatile写操作的前面插入一个StoreStore屏障,防止写volatile与后面的写操作重排序。

    2)在每个volatile写操作的后面插入一个StoreLoad屏障,防止写volatile与后面的读操作重排序。

    3)在每个volatile读操作的后面插入一个LoadLoad屏障,防止读volatile与后面的读操作重排序。

    4)在每个volatile读操作的后面插入一个LoadStore屏障,防止读volatile与后面的写操作重排序。

    (3)不保证原子性
    ​ 原子性指的是,当某个线程正在执行某件事情的过程中,是不允许被外来线程打断的。也就是说,原子性的特点是要么不执行,一旦执行就必须全部执行完毕。而volatile是不能保证原子性的,即执行过程中是可以被其他线程打断甚至是加塞的。

    volatile变量的原子性与synchronized的原子性是不同的。synchronized的原子性是指,只要声明为synchronized的方法或代码块,在执行上就是原子操作的,而volatile是不修饰方法或代码块的,它只用来修饰变量,对于单个volatile变量的读和写操作都具有原子性,但类似于volatile++这种复合操作不具有原子性。

    因此,volatile的原子性是受限制的,在多线程环境中,volatile并不能保证原子性。

  • 相关阅读:
    周赛补题(AcWing、力扣)
    移动软件开发六——APP首页
    springboot uniapp毕业设计-基于Uniapp+SSM实现的Android的网店系统实现的App
    Codeforces Round #809 (Div. 2)
    通信原理板块——信源信宿信息熵、离散信道信息传输速率和信道容量计算
    JVM类加载机制、双亲委派和SPI机制
    深度学习自学笔记六:深层神经网络
    第十届MathorCup高校数学建模挑战赛-A题:无车承运人平台线路定价问题
    centos7 Linux服务器搭建 部署若依 SpringBoot+Vue前后端分离项目 安装配置Mysql Redis Java Nginx
    更新GitLab上的项目
  • 原文地址:https://blog.csdn.net/qq_42077317/article/details/133277727