01.并发多线程-volatile

可见性

  • 多个线程并发读写一个共享变量的时候,有可能某个线程修改了变量的值,可是其余的线程看不到,也就是对其余线程不可见

工做原理

image

  • 主要做用是保证可见性以及有序性
  • 不能保证原子性

volatile 怎么保证可见性和有序性

内存可见性
  1. CPU的内存访问很慢,因此CPU有几层的高速缓存,加速内存访问速度
  2. Java的内存模型对上述又进行了一些列的抽象,JMM(java内存模型)规定全部的变量都在存在主内存的,每一个线程又包含本身的工做内存
  3. 变量在工做内存中修改之后,就会强制将工做内存中的刷回主内存,主内存的变量值立马变成最新的值
  4. 其他线程中工做内存中的变量缓存直接强制失效过时,不容许直接读取和使用,当线程再次准备使用的时候,会在主内存中直接读取最新的值
有序性
  1. JMM是容许编译器和处理器对指令重排序的,可是规定了as-if-serial语义,即无论怎么重排序,程序的执行结果不能改变
  2. 针对多线程出现的问题,加上volatile 会禁止重排序,能够确保程序的有序性
原子性
  1. volatile虽然不能保证原子性,可是在某一些条件下,仍是能提供原子性的
  2. 如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。

底层时间的机制

  • 若是把加入volatile关键字的代码和未加入volatile关键字的代码都生成汇编代码,会发现加入volatile关键字的代码会多出一个lock前缀的指令
  • lock前缀指令实际至关于一个内存屏障
  • 内存屏障提供了一下的功能
    1. 重排序时,不能把后面的指令重排序到内存屏障以前的位置
    2. 使得本CPU的Cacahe写入内存
    3. 写入动做也会引发别的CPU或者别的内核无效化其Cache,至关于让新写入的值对别的线程可见。

应用举例

1. 状态量标记
int a = 0;
volatile bool flag = false;

public void write() {
    a = 2;              //1
    flag = true;        //2
}

public void multiply() {
    if (flag) {         //3
        int ret = a * a;//4
    }
}
复制代码
2.单例模式的实现,典型的双重检查锁定(DCL)
class Singleton{
    private volatile static Singleton instance = null;
 
    private Singleton() {
 
    }
 
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
复制代码

这是一种懒汉的单例模式,使用时才建立对象,并且为了不初始化操做的指令重排序,给instance加上了volatile。java

相关的面试题目
一、Java 中能建立 volatile 数组吗?
  • 能,不过建立的是一个指定数组的引用,而不是整个数据,若是改变引用指向的数据,将会受到volatile的保护,可是若是多个线程同时改变数据的元素,volatile标识符就不能祈祷以前的保护做用了
二、volatile 能使得一个非原子操做变成原子操做吗?
  • 一个典型的例子是在类中的有一个long类型的成员变量,若是你知道该成员变量会被多个线程访问,那么最好将这个成员变量设置为volatile,由于java中读取long类型不是院子的,须要分红两步,若是一个线程正在修改该long变量的值,另外一个此案城可能只能看到该值的通常(前32为)。可是对一个 volatile 型的 long 或 double 变量的读写是原子。
相关文章
相关标签/搜索