zoukankan      html  css  js  c++  java
  • 一个java volatile测试揭开的陷阱

        玩java多线程的,大多都知道volatile:它能保证变量的可见性,其它线程能看到其最新值,但不能用于实现线程安全的变量自增;再深入点的可能知道,它会限制指令重排序,volatile操作前的操作(包括普通变量的读写)不能重排到它之后,反之亦然。

    基于上面的认识,我设计了下面的测试

    public class TestVolatile {
        private volatile int n1=0;//volatile
        private  int n2=0;
    
        public static void main(String[] a) {
            new TestVolatile().test();
            new TestVolatile().test();
        }
    
        public void test() {
            Thread t1 = new Thread() {
                public void run() {
                    do{
                    }while(n2-n1<=0);
                    System.out.println("n2>n1");
                }
            };
            Thread t2 = new Thread() {
                public void run() {
                        for (;n1 < Integer.MAX_VALUE; ) {
                            ++n1;
                            ++n2;
                        }
                        System.out.println("stoped");
                }
            };
            t1.start();
            t2.start();
        }
    }

    对于n1,n2,只有t2线程对其修改,并且n1总是先于n2自增,所以有:

    n1在任何时刻都大于或等于n2

    然后在t1线程里,根据表达式的顺序,先读取n2,再读取n1,因为都在增大,所以后读取的应该比先读取的大,并且n1是volatile的,能保证读到的是最新值,所以应该有

    在t1线程里,表达式n2-n1<=0恒成立

    但事实却并非如此,此测试会输出“n2>n1”(注意:需要让JVM在server模式下进行测试)

    此问题困扰了我几个月,百思不得其解,最近这几天,在一篇深入分析volatile的文章(本文末尾有链接)里找到了答案

    其实volatile并不保证所有情况都不进行重排序,像下面两种情况,是允许指令重排序的

    1、普通变量的读/写操作,然后volatile变量的读操作

    2、volatile变量的写操作,然后普通变量的读/写操作

    再回过来看上面的测试,

    ++n1;可以看作是temp=n1;temp=temp+1;n1=temp;最后执行的肯定是一个写入操作,接着是++n2;是普通操作,按照上面的规则2,此处允许重排序,n2的写入若被排到了n1的写入之前,那么n1>=n2就不是恒成立的了;

    不仅t2线程这出了问题,t1线程里也出了问题

    n2-n1<=0,先是读取普通变量n2,然后是volatile变量n1的读操作,按照规则1,也是可以重排序的,如果先读取了n1,接着t2线程让n1、n2都增加了,t1然后再读了n2,那么完全有可能n2大于n1

    至此问题已经分析清楚了,volatile并不严格保证指令不被重排序。

    既然如此,这算不算是volatile的设计缺陷呢?

    其实这不能算缺陷,因为该测试中,对volatile的使用本身就是不合适的(没加volatile的变量对其它线程的可见性本身就有问题,其值是不确定的),没必为一个错误的用法作出严格的保证

    若想详细了解volatile,请参考此文:http://www.infoq.com/cn/articles/java-memory-model-4

    在此感谢作者 程晓明,解开了我几个月来的困惑。

  • 相关阅读:
    【就业】腾讯VS百度
    MySQL基础知识
    PHP读取远程文件并保存
    【GTK3.0】背景设置
    【GTK】信号量(signal)大全
    c# 调用win32 api
    PHP写窗体程序
    一个苏州IT人的5年挨踢经历面试篇(之二)
    【c++ Primer 】 4.10复习题 12题(int)、(int&)和(int*)
    线段树技巧
  • 原文地址:https://www.cnblogs.com/trytocatch/p/2974547.html
Copyright © 2011-2022 走看看