Volatile
1. 可见性: 当一个共享变量被volatile修饰时,它会保证修改的值会立刻被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性
另外: 通过synchronized和Lock也能保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中,因此可以保证可见性
有序性: 在java内存模型中,允许编译器和处理器对指令进行重新排序,但重新排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。volatile禁止进行指令重排序,在一定程序上可以保证有序性
volatile关键字无法保证对变量的操作的原子性。只能靠变量自身保证原子性。x=0,y=x,x++,其中只有x=10是原子性操作,y=x包含2个操作(先读取x,再讲x的值写入工作内存y中),x++包含3个操作(读取x的值,进行加1,写入心智)
原理和机制: 观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令
每个线程在运行过程中都有自己的工作内存,会将变量的值拷贝一份放在自己的工作内存中,使用volatile会导致线程的工作内存中缓存变量的缓存行无效,需要重新从主存中读取变量(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效)
代码举例
package com.volatiletest;
public class VolatileTest {
public volatile int inc = 0;
public void increase () {
inc++;
}
public static void main(String[] args) {
final VolatileTest test = new VolatileTest();
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
test.increase();
}
}
}.start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(test.inc);
}
}