我们知道volatile只能保证可见性和有序性,加了volatile的内存变量会遵循缓存一致性协议,z这很好的保证了可见性,具体为啥缓存一致性协议可以保证可见性,见笔记并发(一)、MESI(缓存一致性协议)
这篇文章用一个简单的例子解释下为啥volatile不能保证原子性,代码如下;
public class VolatileTest {
private volatile int count = 0;
private void add() {
count++;
}
private void print() {
System.out.println(count);
}
public static void main(String[] args) {
VolatileTest volatileTest = new VolatileTest();
for(int i=0;i<10000;i++) {
new Thread(new Runnable() {
@Override
public void run() {
volatileTest.add();
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
volatileTest.print();
}
}
这段代码执行结果有时候是10000,有时候却到不了10000,为什么呢?
我们知道count++其实是分为两步,第一步是取到count的值,然后再进行加1,所以在内存中该变量的逻辑步骤如下:
第一步
刚开始,count=0在内存中,如下
第二步
然后线程1加载副本到缓存中,由缓存一致性协议可以知道,此时状态为E独占。
第三步
然后线程2也加载到副本缓存中,由缓存一致性协议可以知道,此时两个线程count缓存行的状态为S共享。
第四步
然后线程1把缓存中的count传到CPU进行加一操作,线程2也把缓存中的count传到CPU中进行加1操作
第五步
两个CPU同时修改变量count,并同时向总线发出将各自的缓存行更改为M状态的情况,此时总线会采用相应的裁决机制进行裁决,将其中一个置为M状态,另一个置为I状态,且I状态的缓存行修改无效,这里假设线程1修改成功。
那么线程2的count将会变为I无效的状态,此时,相当于本次轮休白做了。
所以可以得出结论,volatile不能保证原子性。