java:volatile关键字的作用

1、为什么要使用volatile

在java的高并发编程中,每一个线程都会为其开辟其独自的本地内存,不对其它线程共享。该线程的本地内存放有从主内存读取的变量,后续该线程对变量的操作就只会在该线程的本地内存进行,不会对主内存的变量造成影响。

那就会造成一个问题,假设有a和b两个线程,线程a对一个变量c进行操作后,变量c发生了改变,但是在线程b的视角中,变量c并没有发生改变。

对于一个公共变量c,在线程a和b中,就有了两个不同的版本,造成a和b之间信息不同步,在后续要使用变量c的一系列过程中,就可能会出现不可预测的错误。

这时,我们需要让线程a对变量c的操作,对线程b可见,volatile就可以达到这个作用。

2、volatile

volatile是一个关键字,可以加在类的普通变量和静态变量上。可以保障可见性和有序性。

2.1 语义

  1. 在读volatile变量的时候,jmm会将本地内存对应的变量设置为无效,然后从主内存中读取。
  2. 在写volatile该变量的时候,jmm会将本地内存中对应的变量立即刷新回主内存中。

2.2 内存屏障

内存屏障是类同步屏障指令,是cpu或编译器在对内存随机访问的操作中的一个同步点,让此点之前的所有读写操作都执行后才可以执行此点之后的操作,避免代码重排序。

内存屏障其实就是一种jvm指令,java内存模型的重排规则会要求java编译器在生成jvm指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了java内存模型中的可见性和有序性。

内存屏障之前的所有写操作都要回写到主内存中。

内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果。

可以分为四大屏障:

  1. Load1;LoadLoad;Load2:Load1的读操作在Load2及后续读操作之前执行

  2. Store1;StoreStore;Store2:Store2及其后续写操作之前,操作1的写操作已经刷新回主内存中

  3. Load1;LoadStore;Store2:Store2及其后续写操作之前,保证Load1的读操作已经完成

  4. Store1;StoreLoad;Load2:保证Store1的写操作已经刷新回主内存中,Load2及后的读操作才能执行

插入屏障:

  1. volatile读 -> LoadLoad -> LoadStore -> 普通写/读操作
  2. 普通写/读操作 -> StoreStore屏障 -> volatile写 -> StoreLoad

3、volatile的使用细节

java 复制代码
public class Day0412 {

    static int num = 0;

    static volatile int numVolatile = 0;

    static volatile int numVolatileSynchronized = 0;

    static synchronized void addVolatileSynchronized() {
        numVolatileSynchronized++;
    }

    public static void main(String[] args) {

        System.out.println("---------------------------test01------------------");
        test01();

        while (Thread.activeCount() > 2) {
            try {Thread.sleep(1000);} catch (InterruptedException e) {}
        }

        num = 0;
        numVolatile = 0;
        numVolatileSynchronized = 0;

        System.out.println("---------------------------test02------------------");
        test02();

    }

    static void test01() {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    num++;
                }
            }).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    numVolatile++;
                }
            }).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    addVolatileSynchronized();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) {
            try {Thread.sleep(1000);} catch (InterruptedException e) {}
        }

        System.out.println("num;"+num);
        System.out.println("numVolatile;"+numVolatile);
        System.out.println("numVolatileSynchronized;"+numVolatileSynchronized);

    }

    static void test02() {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    num++;
                    numVolatile++;
                    addVolatileSynchronized();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) {
            try {Thread.sleep(1000);} catch (InterruptedException e) {}
        }

        System.out.println("num;"+num);
        System.out.println("numVolatile;"+numVolatile);
        System.out.println("numVolatileSynchronized;"+numVolatileSynchronized);

    }

}

由num、numVolatile和numVolatileSynchronized的数值对比,可以的得出volatile关键字不会保证原子性,在通过test01和test02的对比,可见volatile的内存屏障是真实生效的。

相关推荐
JAVA面经实录91721 小时前
Java企业级工程化·终极完整版背诵手册(无遗漏、全覆盖、面试+落地通用)
java·开发语言·面试
周杰伦fans1 天前
AutoCAD .NET 二次开发:深入理解 EntityJig 的工作原理与正确实现
开发语言·.net
许彰午1 天前
CacheSQL(二):主从复制——OpLog 环形缓冲区与故障自动恢复
java·数据库·缓存
Bat U1 天前
JavaEE|多线程初阶(七)
java·开发语言
谭欣辰1 天前
C++ 排列组合完整指南
开发语言·c++·算法
foundbug9991 天前
自适应滤除直达波干扰的MATLAB实现
开发语言·算法·matlab
XDH_CS1 天前
MySQL 8.0 安装与 MySQL Workbench 使用全流程(超详细教程)
开发语言·数据库·mysql
小短腿的代码世界1 天前
Qt实时盈亏计算深度解析:从持仓数据到动态盈亏展示
开发语言·qt
小康小小涵1 天前
基于ESP32S3实现无人机RID模块底层源码编译
linux·开发语言·python