为什么volatile不能保证原子性(以共享变量自增为例)
为什么volatile不能保证原子性
对于i=1这个赋值操作,由于其本身是原子操作,因此在多线程程序中不会出现不一致问题,但是对于i++这种复合操作,即使使用volatile关键字修饰也不能保证操作的原子性,可能会引发数据不一致问题。
private volatile int i = 0; i++;
如果启了500条线程并发地去执行i++这个操作 最后的结果i是小于500的
复制代码 i++操作可以被拆分为三步:
-
1,线程读取i的值 2、i进行自增计算 3、刷新回i的值
网上一些博客的解释是:
假设某一时刻i=5,此时有两个线程同时从主存中读取了i的值,那么此时两个线程保存的i的值都是5, 此时A线程对i进行了自增计算,然后B也对i进行自增计算,此时两条线程最后刷新回主存的i的值都是6(本来两条线程计算完应当是7)所以说volatile保证不了原子性。
我的不解之处在于:
既然i是被volatile修饰的变量,那么对于i的操作应该是线程之间是可见的啊,就算A.,B两个线程都同时读到i的值是5,但是如果A线程执行完i的操作以后应该会把B线程读到的i的值置为无效并强制B重新读入i的新值也就是6然后才会进行自增操作才对啊。
后来参照其他博客终于想通了:
-
1、线程读取i 2、temp = i + 1 3、i = temp
当 i=5 的时候A,B两个线程同时读入了 i 的值, 然后A线程执行了 temp = i + 1的操作, 要注意,此时的 i 的值还没有变化,然后B线程也执行了 temp = i + 1的操作,注意,此时A,B两个线程保存的 i 的值都是5,temp 的值都是6, 然后A线程执行了 i = temp (6)的操作,此时i的值会立即刷新到主存并通知其他线程保存的 i 值失效, 此时B线程需要重新读取 i 的值那么此时B线程保存的 i 就是6,同时B线程保存的 temp 还仍然是6, 然后B线程执行 i=temp (6),所以导致了计算结果比预期少了1。
后来通过这篇博客 https://www.cnblogs.com/kevinwu/archive/2012/05/02/2479464.html
public class Increment { private int id = 0; public void getNext(){ id++; } }
反编译后的代码
public class Increment extends java.lang.Object{ public Increment(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_0 6: putfield #2; //Field id:I 9: return public void getNext(); Code: 0: aload_0 //加载局部变量表index为0的变量,在这里是this 1: dup //将当前栈顶的对象引用复制一份 2: getfield #2; //Field id:I,获取id的值,并将其值压入栈顶 5: iconst_1 //将int型的值1压入栈顶 6: iadd //将栈顶两个int类型的元素相加,并将其值压入栈顶 7: putfield #2; //Field id:I,将栈顶的值赋值给id 10: return }
2: getfield #2; //Field id:I,获取id的值,并将其值压入栈顶 5: iconst_1 //将int型的值1压入栈顶 6: iadd //将栈顶两个int类型的元素相加,并将其值压入栈顶
确实有一个先将值存入一个临时变量就行操作的过程
-
1、线程读取i 2、temp = i + 1 3、i = temp