美文网首页
volatile关键字

volatile关键字

作者: gaga丶 | 来源:发表于2020-10-15 12:07 被阅读0次

在并发编程中,常遇到的三个问题:原子性,可见性,有序性。volatile可解决可见性和有序性的问题。

1、可见性

当一个程序运行的时候,数据是保存在内存当中的,当cpu正在运行的时候,突然要用到某个数据,就会去内存当中读取这个数据,而cpu去内存当中读取这个数据是非常缓慢的,所以cpu就引入了高速缓存区。内存当中存储的数据,cpu高速缓存区也会存储一份,这样,当cpu频繁读取某个数据的时候,cpu高速缓存区中有,那就可以直接读取cpu高速缓存区的数据,大大提升cpu的工作效率。
这样的工作方式在单线程或者单核多线程下工作是没有问题的,但是到了多核多线程下就会出问题,如果现在有两个线程,线程1和线程2,他们是共享一个内存的,现在有一个数据A=1,核心1从内存中读取数据A,并写入核心1的高速缓存区,核心2也从内存中读取数据A,并写入核心2的高速缓存区,如果线程1修改了数据A=2,它会先更新核心1中的高速缓存区的值,然后写入到内存中,此时内存中A=20,那么当核心2读取数据A时,发现核心2的高速缓存区有A的值,就会直接返回高速缓存区的值,此时你会发现,线程1和线程2访问数据A,得到的值却不相同了。
那么该怎样解决呢?通过volatile。
volatile关键字其中一个作用就是解决可见性的问题,我们给数据A加上volatile关键字,当线程1修改A的值的时候,对于线程2来说是立即可见的。从而保证线程1和线程2读取到的数据是相同的。

2、禁止指令重排

CPU在执行代码时,并不一定会按照我们的编码顺序执行,为了执行效率,它会对哪些先后顺序无关紧要的代码进行重新排序,这就是所谓的指令重排。
但是再多线程的环境下,指令重排会出问题的。

public class Test {

    static boolean b;
    static String str;

    static class Thread1 extends Thread {
        @Override
        public void run() {
            str = "hello world";
            b = true;
        }
    }

    static class Thread2 extends Thread {
        @Override
        public void run() {
            while (!b) {
              
            }
            str.toUpperCase();
        }
    }
}

根据刚才的指令重排理论,Thread1中的 str = "hello world" 和 b = true是没有先后顺序的,如果CPU对Thread1中的代码进行了指令重排,那么Thread2会跳出while循环,str.toUpperCase();会报空指针异常。
我们给变量b加上volatile关键字,就是禁止CPU对b进行指令重排。

volatile static boolean b;

从而保证程序的安全性。

  • 单例模式中的双重检查

我们平常写的双重检查的单例模式:

public class DoubleCheckedLocking { // 1
    private static Instance instance; // 2
    public static Instance getInstance() { // 3
        if (instance == null) { // 4:第一次检查
            synchronized (DoubleCheckedLocking.class) { // 5:加锁
                if (instance == null) // 6:第二次检查
                instance = new Instance(); // 7:问题的根源出在这里
            } // 8
        } // 9
        return instance; // 10
    } // 11
}

instance是没有volatile关键字修饰的,程序也可以很好的运行,但并不是百分百的稳定。
在instance初始化过程中可分为三个步骤:

memory = allocate();    // 1.分配内存对象空间
instance = (memory);   // 2.初始化对象
instance = memory;     // 3.设置instance指向刚分配的内存地址,此时instance != null

2和3代码有可能会被重排序

memory = allocate();    // 1.分配内存对象空间
instance = memory;     // 3.设置instance指向刚分配的内存地址,此时instance != null
instance = (memory);   // 2.初始化对象

在多线程情况下,步骤3中对象初始化没有完成,就相当于这个地址空间内没有实际的值
线程1没有问题,但是线程2会判断instance!=null,线程2就会访问到一个空对象。
解决办法就是给instance加上volatile关键字。

相关文章

网友评论

      本文标题:volatile关键字

      本文链接:https://www.haomeiwen.com/subject/kqgupktx.html