一、作用
volatile关键字的作用有3个,可见性,有序性和对64位的long和double变量的读写保证原子性。
二、作用分析
可见性:多个线程访问同一个共享变量,一个线程修改了共享变量的值,其他线程能够立即可见修改后的值。
有序性:禁止指令的重排序。 CPU对一些原子性操作可能会打乱执行顺序,因为CPU执行每一条指令效率不同,为了优化而打乱原子性的顺序,但是在单线程下结果不会发生变化。
对64位的long和double保证读写的原子性:对于非volatile的64位的long和double类型, JVM允许将64位的读或写操作分为两个32位的操作,如果在多线程情况下,就可能会发生只赋值半个的情况。用volatile进行修饰的话,就能保证64位的读写操作是原子性的。
三、使用场景
1、需要保证多线程中共享变量的可见性时。
2、作为状态标识(多线程中一个线程修改,其他线程读取的情况)
3、懒汉式单例模式double-check单例模式
四、懒汉式单例模式分析
在懒汉式单例模式中:
publice class Singleton{
private static volatile Singleton singleton;
publice statice Singleton getInstance(){
if(singleton == null){
synchronized (singleton.class){
if(singleton == null){
singleton = new Singleton;
}
}
}
}//为什么加volatile和synchronzied?
在懒汉式单例模式中,volatile关键字保证了可见性和禁止指令重排序,加synchronized是因为singleton=new Sinleton不是一个原子性的操作。分为3个阶段,1.在堆内存中申请空间 2.初始化内存空间 3.把内存空间地址赋值给singleton这个变量。 在多线程的情况下,如果线程A只执行12步骤,线程B就进来了,那么B就会执行123步骤,因为singleton还没有被赋值,这种情况singleton可能会初始化两次(B执行完又切换回A了)所以需要加synchronized保证这3个操作是原子性的。 而且如果不加volatile,执行的顺序可能位132.在执行13后切换,这块内存还没有被初始化就被引用了,其他线程使用这个变量的时候则会报错。
五、内存屏障实现原理(线程通信)
volatile如何实现的这3个功能?
通过内存屏障,说内存屏障之前先说一下多线程之间的通信。 在JMM中两个线程要实现通信必须要经过主内存,每一个线程都有一个线程私有的工作内存,主内存是共享的,通信的过程就是先修改自己的工作内存的值,然后写入到主内存中,然后其他线程读取主内存中的值完成通信。而内存屏障的实现原理就是被修饰的变量禁止指令重排序。对于线程中的写操作,写入的数据会强制刷新到主内存中,而读数据会强制从主内存中读取,使工作内存中的变量无效。