Volatile关键字的理解
volatile是java虚拟机提供的最轻量级的同步机制。
当一个变量被定义为volatile之后,他将具备两项特性:
- 第一项是保证此变量对所有线程的可见性
- 第二项是禁止指令重排序优化
1.可见性描述
首先我们谈一谈可见性,“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即知道的。这一个特点是普通变量无法做到的。例如:线程A修改了一个普通变量的值,然后向主内存进行回写,另外一个线程B在线程A会写完成之后再对主内存进行读取操作,新变量才会对线程B可见。
但是这并不代表再多线程进行写操作的时候,基于volatile修饰的关键字是线程安全的。
public class VolatileTest {
public static volatile int race = 0;
public static void increase(){
race++;
}
// 开启二十个线程
private static final int THREADS_COUNT = 20;
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
increase();
}
});
threads[i].start();
}
// 等待所有线程执行完毕
while(Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(race);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLIlcko0-1621511948091)(C:\Users\陈胜\AppData\Roaming\Typora\typora-user-images\image-20210520195211959.png)]
比如说这段代码,开启20个线程,每个线程对race进行1000次++,得到的结果基本上都会小于20000。
这是因为volatile只可以保证再提取值的时候,这个值是正确的并不能保证这个值此刻不会被其他线程过操作并进行修改。如果这个线程执行完毕把此时的race刷回到主存中,则其他线程对这个值的操作将会无效,原子性就得不到保证。
volatile值可以再以下环境中保持原子性,但并不是真正意义上的原子性
- 运算结果并不依赖变量的当前值,或者能够保证只有单一的线程修改变量的值
- 变量不需要与其他的状态变量共同参与不变约束。
2.防止指令重排
指令重排是指处理器采用了允许将多条指令不按程序规定的顺序分开发送给各个对应的电路单元处理。并不是任意重排
关键在于被volatile关键字修饰的变量在2编译后,多执行了一个“lock addl $0x0,(%esp)”操作,这个操作相当于一个内存屏障(指令重排时不能把后面的指令重排序到内存屏障之前的位置),