Holder 类有状态 但不提供setter方法 按理不会抛出AssertionError
再说n != n 自身不相等才为真 但n是基础类型 我认为不会抛异常
瞎猜的。。

解决方案 »

  1.   

    一个线程拿到可能是null的Holder引用,而一个可能拿到初始化好的,这样不是不安全么?
      

  2.   

    好多年没在CSDN回答问题了,出来活动活动筋骨首先LZ要理解什么叫做“正确构造”
    4楼所说的情况属于没有“正确构造”,也就是对象构造期间发生了“this逸出”
    这种情况代码如下所示public class Holder {
        private int n;
     
        public Holder() {
            this.n = n;
            outter = this; //这里this逸出(outter是一个外部使用的对象)
        }
     
        public void assertSanity() {
            if(n != n)
                  throw new AssertionError("This statement is false.");
        }
    }没有正确构造的对象,无论如何不能“安全发布”那么一个正确构造了的对象是否能“安全发布”呢?
    答案是看情况
    如果是“不可变对象”,只要正确构造了,无论如何JVM都能保证它被“安全发布”。
    如果不是“不可变对象”,即使正确构造,但是JVM并不保证它能被“安全发布”。
    差别在哪里呢?就是在多线程环境下,这种没有能“安全发布”情况代码如下所示(使用LZ正确构造的Holder):public class SuperHolder
    {
        public Holder holder;    public static void main(String... args)
        {
              final SuperHolder sh = new SuperHolder();          new Thread(new Runnable(){
                  public void run()
                  {
                       sh.holder = new Holder();
                  }
              }).start();          new Thread(new Runnable(){
                  public void run()
                  {
                       if(sh.holder!=null)
                       {
                            sh.holder.assertSanity();
                       }
                  }
              }).start();
        }
    }这种情况,就可能出现LZ问的这个问题。不过很难观察到,特别在X86架构下。
    为什么会出现这个问题,原因就是构造函数不是原子的,指令可能重排序。LZ明白了吗?
      

  3.   

    ‘但是JVM并不保证它能被“安全发布”’,稍微修改一下:‘但是它的“安全发布”并不由JVM来保证,需要用户自己来保证它的“安全发布”’恩...这样更贴切一些
      

  4.   

    谢谢啊!这么久的第一次给了我。深感荣幸!
    不过,还是不很懂啊。你说的概念我都懂!关键在于,那个n!=n的条件那里,我死都没懂。因为两个都是n,同样的n,不管是否一个一个线程执行holder一个没有赋值,都应该相等啊!可能我比较笨,希望层主包含!可能我的问题也没有表述清楚。我现在很大的一个感受就是,很多时候,一个问题并不是我不知道如何解决,很多时候是我没法把一个问题表述清楚。就像这个问题一样。
      

  5.   


    注意这句“原因就是构造函数不是原子的,指令可能重排序。”
    n != n也不是原子的!
    其编译后的java指令为
    aload
    getfield  //第一次读变量n
    aload
    getfield  //第二次读变量n
    if_icmpeq
    如果不同步的话,两次读变量n之间是可能会切换到其它线程执行的。继续使用我给出的“不安全发布”的例子来说
    第一个线程用来new实例,当第二个线程中 sh.holder!=null 时(也就是构造函数已经发布了),但因为重排序,n有可能尚未赋值,也就是int变量的默认值0。此时第一次读变量n记为n',实际值为0;此时再次切换到线程1完成了n赋值为42,此时第二次读变量n记为n'',实际值为42。
    你所看到的n!=n实际是n'!=n'',最后是0!=42,结果为真
    执行throw new AssertionError("This statement is false.")要是还不明白我就无能为力了
      

  6.   

    一个线程拿到可能是null的Holder引用,而一个可能拿到初始化好的,这样不是不安全么?
    8楼说的很清楚了。
      

  7.   


    注意这句“原因就是构造函数不是原子的,指令可能重排序。”
    n != n也不是原子的!
    其编译后的java指令为
    aload
    getfield  //第一次读变量n
    aload
    getfield  //第二次读变量n
    if_icmpeq
    如果不同步的话,两次读变量n之间是可能会切换到其它线程执行的。继续使用我给出的“不安全发布”的例子来说
    第一个线程用来new实例,当第二个线程中 sh.holder!=null 时(也就是构造函数已经发布了),但因为重排序,n有可能尚未赋值,也就是int变量的默认值0。此时第一次读变量n记为n',实际值为0;此时再次切换到线程1完成了n赋值为42,此时第二次读变量n记为n'',实际值为42。
    你所看到的n!=n实际是n'!=n'',最后是0!=42,结果为真
    执行throw new AssertionError("This statement is false.")要是还不明白我就无能为力了

    懂了懂了!!!大哥,我就是二了一下!真的!泪崩啊!!谢谢!!!
      

  8.   


    注意这句“原因就是构造函数不是原子的,指令可能重排序。”
    n != n也不是原子的!
    其编译后的java指令为
    aload
    getfield  //第一次读变量n
    aload
    getfield  //第二次读变量n
    if_icmpeq
    如果不同步的话,两次读变量n之间是可能会切换到其它线程执行的。继续使用我给出的“不安全发布”的例子来说
    第一个线程用来new实例,当第二个线程中 sh.holder!=null 时(也就是构造函数已经发布了),但因为重排序,n有可能尚未赋值,也就是int变量的默认值0。此时第一次读变量n记为n',实际值为0;此时再次切换到线程1完成了n赋值为42,此时第二次读变量n记为n'',实际值为42。
    你所看到的n!=n实际是n'!=n'',最后是0!=42,结果为真
    执行throw new AssertionError("This statement is false.")要是还不明白我就无能为力了
    求互相关注。还有些问题想求解脱。
      

  9.   

    恩,关注了。不过不常上CSDN,有事还是给我留言吧。
      

  10.   

    Because synchronization was not used to make the Holder visible to other threads, we say the Holder was not properly published. Two things can go wrong with improperly published objects. Other threads could see a stale value for the holder field, and thus see a null reference or other older value even though a value has been placed in holder. But far worse, other threads could see an up-todate value for the holder reference, but stale values for the state of the Holder.[16] To make things even less predictable, a thread may see a stale value the first time it reads a field and then a more up-to-date value the next time, which is why assertSanity can throw AssertionError.