volatile关键字

1.volatile是什么?

  volatile是java虚拟机提供的轻量级的同步机制,具有如下特性:

  a.保证可见性

  b.不保证原子性

  c.禁止指令重排

2.JMM内存模型?

  JMM——https://www.cnblogs.com/zhouxuezheng/p/14950302.html

3.volatile的可见性代码验证:

  主线程中判断资源类Cache中的变量a的值是否为0,如果为0则一直死循环。新开的线程A会在2秒钟后去改变Cache中a的值,但是主线程不能及时获取到a的最新值从而一直死循环,程序并不会在2秒钟结束。即线程A中对Cache中a的修改对main线程而言不可见 。如果在变量a上添加volatile关键字,程序就可在2秒后就可正常退出。

  注意:如果在while代码块中有System.out.println();程序也会在2秒后正常退出,这是因为System.out.println()方法底层有synchronized关键字,这会导致main线程强制获取主存中的数据。

class Cache{
    int a = 0;
    public void setA() {
        a = 2;
    }
}
public class VolatileDemo {
    public static void main(String[] args) {
        Cache cache = new Cache();
        new Thread(() -> {
            try { Thread.sleep(2000); } catch (InterruptedException e) {}
            cache.setA();
        }, "线程A").start();
        while (cache.a == 0) {

        }
    }
}

 4.volatile无法保证原子性

  如果能保证原子性,则输出的结果应该恒等于20000,然而结果不等于20000,会存在写覆盖的问题

class Cache{
    volatile int a = 0;
    public void setA() {
        a++;
    }
}
public class VolatileDemo {
    public static void main(String[] args) {
        Cache cache = new Cache();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    cache.setA();
                }
            }).start();
        }
        try { Thread.sleep(2000); } catch (InterruptedException e) {}
        System.out.println(cache.a);
    }
}

运行结果:

5.如何解决原子性问题呢?

  使用AtomicInteger原子整型类(底层CAS)

6.volatile禁止指令重排理解?

  在多线程环境交替执行set与read,语句1与语句2不存在数据依赖性,由于编译器和CPU优化重排的存在,会导致read中变量一致性无法确定,输出结果无法预测。volatile底层就是在写操作后添加一个store屏障指令,将工作内存中的共享变量刷新回到主内存,在读操作前添加一个load屏障指令,从主存中读取共享变量

class Cache{
    int a = 0;
    boolean flag = false;

    public void set(){
        a = 1; // 语句1
        flag = true; // 语句2
    }
    public void read(){
        if (flag) {
            a = a + 1;
            System.out.println(a);
        }
    }
}

7.你在哪些地方用到过volatile?

  单例模式DCL(double-checked locking)代码

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

8.单例模式volatile分析

  DCL机制不一定线程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排。

  原因在于:某个线程执行到第一次检测,读取到的instance不为null,instance的引用对象可能没有完成初始化。

  instance = new SingletonDemo();可以分成以下3步完成(伪代码):

    memory = allcote(); // 1.分配对象内存空间

    instance(memory); // 2.初始化对象

    instance = memory; // 3.设置instance指向刚刚分配的内存地址,此时instance != null

  步骤2与步骤3不存在数据依赖关系,重排后执行结果在单线程中并没有改变,因此这种重排优化是被允许的

    memory = allcote(); // 1.分配对象内存空间

    instance = memory; // 3.设置instance指向刚刚分配的内存地址,此时instance != null,此时对象还未初始化

    instance(memory); // 2.初始化对象

  指令重排只会保证串行语义的执行一致性(单线程),但并不关心多线程间的语义一致性

  所以当一条线程方位到instance不为null时,instance的实例不一定初始化完成,也就造成了线程不安全的问题

posted @ 2021-07-01 22:34  一柒微笑  阅读(53)  评论(0编辑  收藏  举报