Java虚拟机提供的最轻量级的同步机制
使用volatile关键字修饰变量后,当一个线程对该变量进行了修改,其他线程可以立即得知该变量的新值
但是基于volatile变量运算在并发下并不是线程安全的,因为Java里面的运算操作符并非原子操作,举例说明如下
// 按正常来说,结果应该为500,但是得到的结果总是小于500,因为自增操作不是原子性操作
public class TestJUC {
?
// volatile变量
public static volatile int race = 0;
?
// volatile变量自增操作
public static void increase(){
race ++;
}
?
// 线程数量
private static final int THREADS_COUNT = 10;
?
public static void main(String[] args) {
?
// 线程数组
Thread[] threads = new Thread[THREADS_COUNT];
?
// 每个线程都调用自增方法
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
依据自增操作说明原子性
通过反编译race ++,得到的字节码指令如下
public static void increase();
Code:
Stack = 2, Locals = 0, Args_size = 0
0: getstatic #13;
3: iconst_1
4: iadd
5: putstatic #13;
8: return
LineNumberTable:
line 14: 0
line 15: 8
当getstatic指令取到race的值放到操作栈顶时,volatile关键字保证了race的值在此时是正确的
但是在执行后续指令时,其他线程可能已经改变了race的值
此时操作栈顶的值就变成了过期的数据
所以putstatic指令执行后可能把较小的race值同步回了主内存中,就会出现数据错误问题
在不符合以下两条规则的运算中,我们要通过加锁保证原子性
运算结果并不依赖变量的当前值,或者能够保证只有单一的线程修改变量的值
变量不需要与其他的状态变量共同参与不变约束
在硬件架构上,指令重排序是值处理器允许将多条指令不按照出现规定的顺序,分开发送给各个相应的电路单元进行处理,但是需保障重新能得到正确的结果
指令1:A = A + 10;
指令2:A = A(指令1执行后得到的A) * 2;
指令3:B = B - 3;
?
指令2依赖于指令1的值,所以指令1和指令2的顺序不能重排
?
但是指令3不存在与其他指令之间的依赖关系,所以指令3的顺序可以被重排到指令1、2之前或中间,只要保证处理器执行后面依赖于A、B值的操作时能获取到正确的A和B的值
volatile修饰的变量,变量修改之后,会存在一个内存屏障
重排序时不能把后面的指令重排序到内存屏障之前的位置
假设T表示一个线程,V和W分别表示两个volatile变量,在进行read、load、use、assign、store、write操作时需要满足以下规则
1、T操作变量V,use的前一个动作必须是load,load的后一个动作必须是use
在工作内存中,每次使用V前都必须先从主内存中刷新最新的值,用于保证能看见其他线程对变量V所做的修改
2、T操作变量V,store的前一个动作必须是assign,assign的后一个动作必须是store
在工作内存中,每次修改V后都必须立刻同步到主内存中,用于保证其他线程可以看到自己对变量V所作的修改
3、动作A:T操作变量V,use或assign
动作B:T操作变量V,和A相关联的load活store
与此类似:
动作X:T操作变量W,use或assign
动作Z:T操作变量W,和X相关联的load活store
动作Y:T操作变量W,和Y相关联的read活write
如果A优于W,那么C由于Y
原文:https://www.cnblogs.com/LittleSkinny/p/14420083.html