参考网页
https://www.cnblogs.com/dolphin0520/p/3920373.html#!comments
JDK版本--代码中打印了jdk的版本
1.7.0_79
例子代码--Test.java
package volatileTest;
/**
* @Description TODO
* @Date 2018/8/18 11:58
* @Version 1.0
**/
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
System.out.println(System.getProperty("java.version"));
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println("---" + test.inc);
}
}
运行结果--多次运行
运行结果1
1.7.0_79
---9836
运行结果2
1.7.0_79
---10000
运行结果3
1.7.0_79
---9000
运行结果4
1.7.0_79
---9882
运行结果5
1.7.0_79
---10000
运行结果6
1.7.0_79
---10000
结论
结果有10000,也有几次不是10000的。不确定,不稳定,也就是说volatile无法保证操作的原子性。
为什么volatile无法保证操作的原子性?
作者解释
大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。
可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。
这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
在前面已经提到过,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
假如某个时刻变量inc的值为10,
线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;
然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值是10,然后进行加1操作,并把11写入工作内存,最后写入主存。
然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
那么两个线程分别进行了一次自增操作后,inc只增加了1。
解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。
根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。
★网友回答
★一个基本常识--计算机执行指令只会向前,不会后退的
Inc++的操作分为3步
【操作1】(因为 volatile 的原因)CPU从主存中获取inc的值,此时inc的值已经到了CPU中
【操作2】CPU对 inc 的值进行 +1 的操作
【操作3】CPU将 inc + 1 后的值写回到工作内存,然后写回到主存
★为什么volatile无法保证操作的原子性?
实际上没有那么多的为什么。
因为java规范中本来也没说volatile要保证原子性啊。
假设有线程A和线程B。假设此时此刻inc的值为10。
线程A进行了【操作1】,然后失去了锁。注意,此时线程A已经读取了inc的值,线程A不会再读取第二次了。由于inc被volatile修饰,此时线程A能保证自己读取的inc肯定是最新的值。但是,计算机指令是不会回退的,只会向前进行。
然后线程B完成了【操作1】【操作2】【操作3】,然后把锁交还给了线程A。注意,此时,主存中inc值已经变成了加1后的值即11。但是,计算机指令是不会回退的,只会向前进行,所以线程A会继续按照读取的inc为10进行后面的指令,所以线程A会继续【操作2】【操作3】,然后再次把inc加1后的值即11写回主存。
所以,虽然线程A和线程B都进行了 +1 的操作,但是inc的值还是11。