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的内存屏障是真实生效的。

相关推荐
恼书:-(空寄2 小时前
责任链模式实现流程动态编排
java·责任链模式
APIshop2 小时前
Java获取淘宝商品价格、图片与视频:淘宝开放平台API实战指南
开发语言·python
XiYang-DING2 小时前
【Java】Map和Set
java·开发语言
小则又沐风a2 小时前
STL库: string类
开发语言·c++
菜菜小狗的学习笔记2 小时前
八股(二)Java集合
java·开发语言
星乐a2 小时前
String 不可变性与常量池深度解析
java·开发语言
captain3762 小时前
ACM模式下Java输入输出函数为什么会超时?及解决方法
java·开发语言
程序员老邢2 小时前
【产品底稿 04】商助慧 V1.1 里程碑:爬虫入库 + MySQL + Milvus 全链路打通
java·爬虫·mysql·ai·springboot·milvus
2601_950703942 小时前
Java安全编程与静态分析实战
java