volatile保证原子性吗?

参考网页

http://www.javashuo.com/article/p-hsugcpqq-hw.htmlhtml

JDK版本--代码中打印了jdk的版本

1.7.0_79java

例子代码--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_79c++

---9836缓存

运行结果2

1.7.0_79app

---10000spa

运行结果3

1.7.0_79线程

---9000code

运行结果4

1.7.0_79htm

---9882blog

运行结果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。

相关文章
相关标签/搜索