java并发编程之四:volatile的使用及其原理

1.volatile的作用

我们已经提到过可见性、有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果对Synchronized原理有了解的话,应该知道Synchronized是一个比较重量级的操作,对系统的性能有比较大的影响,所以,如果有其他解决方案,我们通常都避免使用Synchronized来解决问题。而volatile关键字就是Java中提供的另一种解决可见性和有序性问题的方案。对于原子性,需要强调一点,也是大家容易误解的一点:对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。

2.volatile的使用,禁止指令重排
public class SingletonDemo4 {
    private SingletonDemo4(){}
    //volatile关键字是保证线程可见性和禁止指令重排
    private volatile static SingletonDemo4 instance;
    public static SingletonDemo4 getInstance(){
        if(instance==null){
            synchronized (SingletonDemo4.class){
                if(instance==null){
                    /*
                     *instance=new SingleTonDemo4()其实在jvm中执行了三步:
                     * 1.在堆中开辟一块内存大小为SingletonDemo4实例所占空间
                     * 2.使用new SingletonDemo4()初始化对象实例放入堆开辟的内存中
                     * 3.将内存中的指针引用赋值给虚拟机栈中的局部变量instance。
                     */
                    instance=new SingletonDemo4();
                }
            }
        }
        return instance;
    }
}

现在我们分析一下为什么要在变量singleton之间加上volatile关键字。要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:

(1)分配内存空间。

(2)初始化对象。

(3)将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

(1)分配内存空间。

(2)将内存空间的地址赋值给对应的引用。

(3)初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。

3.可见性

可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。volatile关键字能有效的解决这个问题.线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作。这也是导致线程间数据不可见的本质原因。因此要实现volatile变量的可见性,直接从这方面入手即可。对volatile变量的写操作与普通变量的主要区别有两点:
(1)修改volatile变量时会强制将修改后的值从线程工作内存刷新到主内存中。

(2)修改volatile变量后会导致其他线程工作内存中对应的共享变量失效。因此,在读取该变量值的时候就需要重新读取主内存中的值到线程的工作内存
通过这两个操作,就可以解决volatile变量的可见性问题。

4.无法保证原子性

volatile只能保证对单次读/写的原子性,针对i++这样的操作因为牵涉到读和写所以无法保证原子性:
(1).读取内存中i的值
(2).将i+1计算
(3)将i的值回写到内存
volatile是无法保证这三个操作是具有原子性的,我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。

总结

总体来说,volatile是并发编程中的一种优化,在某些场景下可以代替Synchronized。但是,volatile的不能完全取代Synchronized的位置,只有在一些特殊的场景下,才能适用volatile。总的来说,必须同时满足下面两个条件才能保证在并发环境的线程安全:
(1)对变量的写操作不依赖于当前值。
(2)该变量的计算方式中没有包含其他变量。

你可能感兴趣的:(java并发编程之四:volatile的使用及其原理)