内存可见性
理解volatile变量首先要知道一个重要的概念:内存可见性
。可见性是一种复杂的属性,因为它的行为总是跟我们的直觉相悖。我们知道在一个单线程的任务中,如果先向一个变量赋值,再读取这个变量,一定能获得这个变量赋值后的状态。但在读操作和写操作在不同线程的情况下,却不是这样,因为我们无法保证执行读操作的线程能适时的看到其他写操作线程写入的值。
下面这段代码,主线程作为写线程,启动一个读线程来修改ready和number的值,读线程判断ready的值为true就输出number。看起来会输出42,但也有可能输出0,或者无法终止,因为代码中没有足够的同步机制来保证主线程写入ready和number时对读线程是可见的。
输出0是因为读线程看到了ready修改后的值,却没有看到number修改后的值,这种现场被称为重排序
,在没有同步的情况下,编译器、处理器以及运行时等都有可能对操作的执行顺序进行一些意想不到的调整,其他线程无法获得当前线程的重排序情况,所以对其他线程来说不能保证当前现场按照代码顺序执行。这看起来有些复杂的问题,其实有一个非常简单的解决办法,就是只要数据在多个线程间共享,就使用正确的同步方式对他们进行同步。
volatile变量
volatile变量就是Java提供的一种稍弱的同步方式,用来确保变量的更新操作通知到其他线程。当把变量声明为Volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者其他对处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新的写入值。
volatile变量通常用做某个操作完成、发生中断或者状态的标志。下面给出了一种volatile的常见用法:检查某个状态标记来判断是否退出循环:
通过这种方式,当asleep被另一个线程修改时,执行判断的线程可以发现asleep的变化。
但是,volatile不能代替加锁机制,因为volatile只能保证原子性而不能保证可见性
。列如,volatile的语义不能确保递增操作,如count++,的原子性,如果有多个线程执行count++操作,volatile无法保证正常的执行结果。而原子变量(如AtomicInteger、AtomicReference)提供了原子性和可见性,它们也经常被当做一种更好的volatile变量。
因为有上述的限制,只有满足下列条件时,才能使用volatile变量:
- 对变量的写入操作不依赖变量的当前值,或者只有单个线程对变量进行写入操作。
- 该变量不会与其他状态变量一起纳入不变型条件。
- 在访问变量时不需要加锁。