使用volatile是另一种使得class线程安全的方法,类似于synchronized,atomic wrapper。线程安全是一个方法或者类实例同时被多个线程使用,但是不会产生任何问题。
如下面代码
class SharedObj
{
// Changes made to sharedVar in one thread
// may not immediately reflect in other thread
static int sharedVar = 6;
}
假设两个线程使用SharedObj,这两个线程在不同的处理器上运行,每个线程拥有sharedVariable变量的本地副本,如果一个线程修改这个值,修改后的值并不会马上反馈到main memory的原始值,这依赖缓存的写策略,那么另一个线程并不知道修改后的值,会导致数据不一致。
下面图显示,如果两个线程运行在不同的处理器,sharedVariable变量的值在不同的线程可能会不一样。
没有任何同步操作,变量的修改可能会对读线程不可见,尽管现代硬件提供很好的缓存一致性,会引起一个缓存的值修改反馈到另一个缓存的值,但是依赖于硬件来修复错误的应用程序并不会很好的方法。
class SharedObj
{
// volatile keyword here makes sure that
// the changes made in one thread are
// immediately reflect in other thread
static volatile int sharedVar = 6;
}
volatile并不会与static冲突,static变量是class members,是在所有实例中共享的,仅仅在main memory中有一个副本。
volatile vs synchronized
两个重要特性
- 互斥:在一个时间点仅仅有一个线程可以执行代码块
- 可见性:一个线程对共享变量的修改对其他线程可见
Java的synchronized关键字既保证互斥,也保证可见性。如果我们使线程修改共享变量的代码块synchronzied,那么仅仅有一个线程可以进入代码块,修改变量值,并反馈到main memory。其他线程同时想进入代码块,会被阻塞。
有一些时候,我们可能仅仅想可见性,并不要求原子性。在这种情况下使用synchronized关键字可能会比较负重,引起扩展性问题。这种场景下适合使用volatile。volatile变量具有synchronized的可见性特性,但是不具有原子性特性。volatile变量的值并不会缓存,所有的写和读将会从main memory中完成。volatile的使用仅限于有限的使用场景,因为大部分原子性是必要的。例如,一个简单的增加操作,x=x+1或者x++看起来是单个操作,但是实际上是一个复合的(读-修改-写)操作,这种操作必须具有原子性。
Java中的volatile是告诉编译器这个变量的值并不会缓存。