1、为什么要使用volatile
在java的高并发编程中,每一个线程都会为其开辟其独自的本地内存,不对其它线程共享。该线程的本地内存放有从主内存读取的变量,后续该线程对变量的操作就只会在该线程的本地内存进行,不会对主内存的变量造成影响。
那就会造成一个问题,假设有a和b两个线程,线程a对一个变量c进行操作后,变量c发生了改变,但是在线程b的视角中,变量c并没有发生改变。
对于一个公共变量c,在线程a和b中,就有了两个不同的版本,造成a和b之间信息不同步,在后续要使用变量c的一系列过程中,就可能会出现不可预测的错误。
这时,我们需要让线程a对变量c的操作,对线程b可见,volatile就可以达到这个作用。
2、volatile
volatile是一个关键字,可以加在类的普通变量和静态变量上。可以保障可见性和有序性。
2.1 语义
- 在读volatile变量的时候,jmm会将本地内存对应的变量设置为无效,然后从主内存中读取。
- 在写volatile该变量的时候,jmm会将本地内存中对应的变量立即刷新回主内存中。
2.2 内存屏障
内存屏障是类同步屏障指令,是cpu或编译器在对内存随机访问的操作中的一个同步点,让此点之前的所有读写操作都执行后才可以执行此点之后的操作,避免代码重排序。
内存屏障其实就是一种jvm指令,java内存模型的重排规则会要求java编译器在生成jvm指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了java内存模型中的可见性和有序性。
内存屏障之前的所有写操作都要回写到主内存中。
内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果。
可以分为四大屏障:
-
Load1;LoadLoad;Load2:Load1的读操作在Load2及后续读操作之前执行
-
Store1;StoreStore;Store2:Store2及其后续写操作之前,操作1的写操作已经刷新回主内存中
-
Load1;LoadStore;Store2:Store2及其后续写操作之前,保证Load1的读操作已经完成
-
Store1;StoreLoad;Load2:保证Store1的写操作已经刷新回主内存中,Load2及后的读操作才能执行
插入屏障:
- volatile读 -> LoadLoad -> LoadStore -> 普通写/读操作
- 普通写/读操作 -> StoreStore屏障 -> volatile写 -> StoreLoad
3、volatile的使用细节
java
public class Day0412 {
static int num = 0;
static volatile int numVolatile = 0;
static volatile int numVolatileSynchronized = 0;
static synchronized void addVolatileSynchronized() {
numVolatileSynchronized++;
}
public static void main(String[] args) {
System.out.println("---------------------------test01------------------");
test01();
while (Thread.activeCount() > 2) {
try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
num = 0;
numVolatile = 0;
numVolatileSynchronized = 0;
System.out.println("---------------------------test02------------------");
test02();
}
static void test01() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
num++;
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
numVolatile++;
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
addVolatileSynchronized();
}
}).start();
}
while (Thread.activeCount() > 2) {
try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
System.out.println("num;"+num);
System.out.println("numVolatile;"+numVolatile);
System.out.println("numVolatileSynchronized;"+numVolatileSynchronized);
}
static void test02() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
num++;
numVolatile++;
addVolatileSynchronized();
}
}).start();
}
while (Thread.activeCount() > 2) {
try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
System.out.println("num;"+num);
System.out.println("numVolatile;"+numVolatile);
System.out.println("numVolatileSynchronized;"+numVolatileSynchronized);
}
}

由num、numVolatile和numVolatileSynchronized的数值对比,可以的得出volatile关键字不会保证原子性,在通过test01和test02的对比,可见volatile的内存屏障是真实生效的。