# volatile详解


# volatile的作用

  • 防止重排
  • 实现可见性
  • 保证原子性:单次读写

# 防重排序

我们从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。其源码如下:

ublic class Singleton {
    public static volatile Singleton singleton;
    /**
     * 构造函数私有,禁止外部实例化
     */
    private Singleton() {};
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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

  • 分配内存空间。
  • 初始化对象。
  • 将内存空间的地址赋值给对应的引用。

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

  • 分配内存空间。
  • 将内存空间的地址赋值给对应的引用。
  • 初始化对象

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

# 实现可见性

可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。volatile关键字能有效的解决这个问题,我们看下下面的例子,就可以知道其作用:

public class TestVolatile {
    private static boolean stop = false;

    public static void main(String[] args) {
        // Thread-A
        new Thread("Thread A") {
            @Override
            public void run() {
                while (!stop) {
                }
                System.out.println(Thread.currentThread() + " stopped");
            }
        }.start();

        // Thread-main
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println(Thread.currentThread() + " after 1 seconds");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stop = true;
    }
}

>> Thread[main,5,main] after 1 seconds
// Thread A一直在loop, 因为Thread A 由于可见性原因看不到Thread Main 已经修改stop的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

可以看到 Thread-main 休眠1秒之后,设置 stop = ture,但是Thread A根本没停下来,这就是可见性问题。如果通过在stop变量前面加上volatile关键字则会真正stop:

Thread[main,5,main] after 1 seconds
Thread[Thread A,5,main] stopped

Process finished with exit code 0

1
2
3
4
5

# 保证原子性:单次读/写

volatile不能保证完全的原子性,只能保证单次的读/写操作具有原子性。

*** i++为什么不能保证原子性?***

对于原子性,需要强调一点,也是大家容易误解的一点:对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。

# volatile 的实现原理

//TODO