Java 中的 volatile
volatile 是Java虚拟机提供的轻量级的同步机制。
- 保证可见性
- 不保证原子性
- 禁止指令重排
可见性
当多个线程操作同一个变量时,每个线程都会将该变量拷贝一份到自己工作内存中,然后线程完成操作之后,将变量写回主内存。因此可见性就是一个线程在修改变量之后,回通知其他线程,告知改变量已经被修改了。
首先创建一个资源类
class MyData {
volatile int number = 0;
public void setNumber () {
this.number = 60;
}
public void numberPlus() {
this.number++;
}
}
// 多线程下测试
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(myData.number);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.setNumber();
System.out.println(myData.number);
}, "AAA").start();;
while(myData.number == 0) {
}
}
当使用 volatile
修饰 number
之后,程序回输出 0 和 60 之后执行完成退出。
当没有使用 volatile
修饰 number
,程序回输出 0 和 60 之后一直保持执行(死循环),不退出。
由此可以看出,volatile
修饰的变量在修改后,回通知其他线程,保证可见性。
原子性
class MyData {
volatile int number = 0;
public void setNumber () {
this.number = 60;
}
public void numberPlus() {
this.number++;
}
AtomicInteger atomicInteger = new AtomicInteger(); // 默认为0
public void atomicAdd() {
atomicInteger.getAndIncrement();
}
}
public static void main(String[] args) {
MyData myData = new MyData();
for(int i=0; i<20; i++) {
new Thread(() -> {
for(int j=0; j<1000; j++) {
myData.numberPlus();
myData.atomicAdd();
}
}, String.valueOf(i)).start();;
}
while(Thread.activeCount() >2) {
Thread.yield();
}
System.out.println(myData.number);
System.out.println(myData.atomicInteger);
}
程序输出 number
的值小于20000,atomicIntege
r等于20000。
因为 volatile
不保证原子性,所以在多线程下操作 number
会出现值被覆盖的问题,也就是一个线程还没有完成++,另一个线程就取出了 number
进行++, 最后导致值小于了 20000。
解决 volatile
原子性问题,可以使用 AtomicInteger
。
指令重排
为了提高性能,编译器和处理器常常会进行指令重排,在多线程环境下,由于指令重排,导致代码的执行顺序和我们书写代码顺序不一致,最终变量能否保持一致是无法确定的,结果无法预测。