volatile概念
谈到volatile,理解原子性和易变性是不同的概念这一点很重要,volatile是轻量级的锁,它只具备可见性,但没有原子特性。如果你将一个域声明为volatile,那么只要对这个域产生了写操作,所有的读操作都可以看到这个修改,即便使用了本地缓存也一样,volatile会被立即写入到主内存中,而读的操作就发生在主内存中。在非volatile域上的原子操作不必刷新到主内存中,所以读操作的任务看不到这个值,如果多个任务在同时访问某个域,那么这个域就应该是volatile,否则这个域就应该经过同步来访问,同步也会导致向主内存中刷新。
使用volatile而不是synchronize的唯一安全的情况就是类中只有一个可变的域。个人认为,第一选择应该是synchronize,这应该最安全的方式 。如果并发水平不高,最好还是不要使用。
volatile使用
在使用volatile变量时,应当考虑是否满足下面这样的要求:
对变量的写入操作不依赖变量的当前值
说白了volatile 变量不能用作线程安全计数器,类似于i++这种增量操作。增量操作符++不是原子的。这个操作分解开来看是先从堆内存中获得i值的副本放到缓存中,然后对副本值加1,最后再将副本值写回到堆内存的变量i中,是一个由读取-修改-写入操作序列组成的组合操作。
没有用于其它变量的不变式条件中(lower小于upper)
/*
* volatile只保证lower与upper的最后写入一定会被其它读取的线程看到
* 但不能保证在lower或upper写入时,另一个变量的值没有发生变化
*/
private volatile int lower;
private volatile int upper;
public int getLower() {
return lower;
}
public int getUpper() {
return upper;
}
public void setLower(int lower) {
if (lower > upper)
throw new IllegalArgumentException();
this.lower = lower;
}
public void setUpper(int upper) {
if (upper < lower)
throw new IllegalArgumentException();
this.upper = upper;
}
volatile变量就不适合用于不变性条件这种情况,以上下限为例,lower必须小于upper,这就是一种不变性条件,你可以理解为这是一种规则限制。它们都只有set与get方法,但在set方法里面加入了约束条件,这时,volatile的可见性就不能保证并发时,lower与upper之间的不变性条件(lower小于upper)一定成立了。
曾经见到过这样的一个面试题:
volatile 能使得一个非原子操作变成原子操作吗?
答案是能的,在基本数据类型中long和double是非原子性的,double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,如果一个线程正在修改该 long 变量的值,另一个线程可能只能看到该值的一半(前 32 位)。但是将long与double加上volatile ,对变量的读写是原子性的
volatile的作用
volatile不是保护线程安全的。它保护的是变量安全。主要的功能是保护变量不被主函数和中断函数反复修改造成读写错误。
volatile具备两种特性:
- 保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的。
- 禁止指令重排序优化。
Volatile和Synchronized四个不同点:
1 粒度不同,后者锁对象和类,前者针对变量
2 syn阻塞,volatile线程不阻塞
3 syn保证三大特性,volatile不保证原子性
4 syn编译器优化,volatile不优化