一、作用

1、保障线程可见性

main线程和T线程共享堆内存的数据;main和T也有本人的工作空间,当要拜访共享内存的数据flag,会把共享内存的flag复制一份到本人的工作空间中。

例如main线程对flag进行了扭转,首先是在本人的空间进行扭转,批改后的值会马上批改到共享内存;然而T线程何时查看共享内存的值有没有被扭转不好管制。

即 main线程的批改并没有及时的反馈到T线程,也就是线程之间不可见。对这个变量加了volatile后,可能保障一个线程对这个变量批改后,另一个线程可能马上晓得。

底层是通过CPU的缓存一致性协定(MESI)保障的

2、禁止指令重排序

指令重排序:CPU原来执行一条指令时,是一步一步的程序执行;当初的CPU为了提高效率,会并发的执行指令,就是第一个指令执行到一半时,第二个指令可能曾经开始执行了,也就是流水线似的执行。这时就要求编译器,要可能对指令重排序产生影响的状况进行相应的解决,此时volatile就派上用场了。

DCL单例(Double Check Lock)来解释

/** * @author Java和算法学习:周一 */public class Singleton {    private static volatile Singleton INSTANCE;    private Singleton() {    }    public static Singleton getInstance() {        if (INSTANCE == null) {            //双重查看            synchronized (Singleton.class) {                if (INSTANCE == null) {                    try {                        Thread.sleep(1);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    INSTANCE = new Singleton();                }            }        }        return INSTANCE;    }    public static void main(String[] args) {        for (int i = 0; i < 100; i++) {            new Thread(() -> {                System.out.println(Singleton.getInstance().hashCode());            }).start();        }    }    }

INSTANCE = new Singleton();(如果外面有个变量int a = 100)new对象的过程分为3步
1)申请内存(赋默认值);a=0
2)初始化;a=100
3)赋值,把值给变量;把a的值赋值给INSTANCE,即让INSTANCE指向变量a的地址

如果外面存在指令重排序的话,就存在还未初始化的变量就进行了赋值操作,即2、3两步地位替换了;也就是对象处于半初始化状态就进行了赋值操作。当第一个线程(只管加了锁),执行到new Singleton()时,new了一半;此时第二个线程来了,首先判断INSTANCE 是否为空,因为INSTANCE曾经是半初始化状态,外面曾经有值了,不再是空值了,也就是第二个线程曾经拿到了这个对象了,这个线程就能够间接应用该对象了,很可能就会应用外面的这个值。原本冀望这个值是100,然而这个值却是0,如果这个值是订单数的值这时就存在问题。加了volatile后,对这个对象的指令重排序就不容许存在,即必须是在初始化实现之后才会进行赋值操作

3、volatile不能保障原子性

/** * @author Java和算法学习:周一 */public class T {    public volatile int count = 0;    public synchronized void m() {        for (int i = 0; i < 1000; i++) {            count++;        }    }    public static void main(String[] args) {        T t = new T();        List<Thread> threadList = new ArrayList<>();        for (int i = 0; i < 10; i++) {            threadList.add(new Thread(t::m));        }        threadList.forEach((o)->{            o.start();        });        threadList.forEach((o)->{            try {                o.join();            } catch (InterruptedException e) {                e.printStackTrace();            }        });        System.out.println(t.count);    }}

m办法,如果不加synchronized,count始终不会到10000。起因是,当一个线程把count值批改为1后,此时进来了第二、第三个线程,都读到count为1,批改后把count加1(即2)写回去,这时两个线程对count批改后,count的值只从1变到了2,所以最初的后果总是小于10000。归根结底就是count的值是保障了可见性,然而count++自身不是原子性的操作(底层分为几个步骤)。

volatile能保障线程的可见性,然而并不能代替synchronized保障原子性。