volatile的特点
- 保证此变量对所有的线程的可见性。这里的可见性是指当一个线程修改了这个变量的值,新值对于其他线程是立即可以得知的。普通变量不可以,因为普通变量的值在线程间传递均需要通过主内存来完成,也就是说每次都要去主存中读写操作
- 禁止指令重排优化
- volatile 并不能保证线程安全
测试代码
public class VolatileTest {
public static volatile int race = 0;
public static void increment() {
race++;
}
private static final int THREAD_COUNT = 20;
public static void main(String[] args) {
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++)
increment();
}
});
threads[i].start();
}
while (Thread.activeCount() > 1)
Thread.yield();
System.out.println(race);
}
}
这段代码每次运行的结果都不一样,也没有输出期望的200000.
原因
由非原子操作的运算导致的。
javap 反编译查看源码
public static void increment();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field race:I
3: iconst_1
4: iconst_1
5: putstatic #2 // Field race:I
8: return
LineNumberTable:
line 7: 0
line 8: 8
可以看到,race++
是由四条指令完成的,
getstatic
把race
的值取到操作栈顶,volatile关键字保证race的值在此时是正确的。但是在执行
iconst_1
和iconst_1
时,其他线程可能已经把race的值加大了,而操作数栈栈顶的值就是过期数据,所以putstatic
指令执行后,就可能把较小的race值同步回主存之中。
即使只有一条字节码指令,但是也不能保证就是一个原子操作
volatile不适合的场景
- 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
- 变量不需要与其他的状态变量共同参与不变约束
volatile的性能
某些情况下,volatile的性能要优于synchronized和lock,由于虚拟机对锁实行的许多消除和优化,是的我们很难的认为volatile比synchronized快多少。
volatile变量的读操作与普通变量相差无几,但是写操作可能会慢一下,因为它要在本地代码中插入许多内存屏障来保证处理器不发生乱序执行。
再谈Java内存模型
Java内存模型是围绕在病发过程中如何处理原子性、可见性和有序性这三个特征来建立的。
原子性(Atomicity
)
由Java内存模型来直接保证原子性变量操作有read,load,assign,use,store,write
等。我们可以大致的认为基本数据类型的读写访问是原子性的(long和double是例外)
为了提供原子性保证,Java提供了monitorinter 和 monitorexit
指令来保证,反映在代码中就是synchronized
,也就是说,synchronized
代码块之间的操作也具有原子性.
可见性(Visibility
)
可见性是指当一个线程修改了共享的值,其他线程能立即得到这个修改。
Java内存模型是通过在变量修改后将新值同步回主存,在变量读取前从主存刷新变量值这种依赖主存作为传递媒介的方式来实现可见性的,无论是volatile还是普通变量都是。volatile的不同之处在于,volatile的特殊规则保证了新值能够立即同步到主内存,以及每次使用前立即从主内存刷新
除了volatile关键字之外,还有两个关键字能够实现可见性,即synchronized
和final
,synchronized
实现可见性的原理是,在执行unlock
之前,必须把此变量同步到主存中(执行store、write)操作。
final
实现可见性在于一旦final修饰的字段初始化完成,在构造器没有把this的引用传递出去,那么其他线程就可以看见final修饰的变量的值。
有序性
使用synchronized
和volatile
保证有序性。