Java中的可变可见性

时间:2013-08-24 09:59:12

标签: java

在下面的代码中,为什么我不能从另一个线程中看到变量“i”?

public class Main {
    public static void main(String[] args) {
        int i = 0;
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(i);
            }
        }).start();
    }
}

为什么我可以在下面的代码中看到它?

public class Main {
    int i = 0;      
    public static void main(String[] args) {
        new Main().method();
    }

    private void method() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                i = 1;
            }
        }).start();
    }
}

3 个答案:

答案 0 :(得分:25)

来自docs

  

与实例方法和变量一样,内部类是关联的   使用其封闭类的实例并可直接访问它   对象的方法和领域。

enter image description here

现在先拿第二个例子,当在父类中声明i时,内部类可以访问它,因为内部类可以访问整个父对象。 i从内部类正确引用为Main.this.i。现在编译器在内部类中秘密地复制this.ithis永远不会在对象内部发生变化,this.i将指向内部和外部类的正确对象。编译器很高兴。

在第一个示例中,i不是父类的一部分,它在方法中声明,因此它的引用现在是在堆栈上,而不是在堆上。 (在这种情况下,i无法生活在上图所示的大外圆上)。现在编译器必须秘密地在内部类中复制i。但是害怕方法的i可能会在堆栈上发生变化。编译器不能在这里使用外部类连接到i,并且每次更改时都无法从堆栈中复制i引用。因此,决定必须使方法的i引用不可更改,换句话说,final

Java播放的晦涩秘密技巧让您可以从内部类访问外部字段,详细说明如下:http://techtracer.com/2008/04/14/mystery-of-accessibility-in-local-inner-classes/

在这里讨论了更多“为什么”:http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg04044.html

tl; dr:实例字段可由内部类直接访问,本地字段必须最终可由内部类访问。

答案 1 :(得分:10)

Java仅允许您访问匿名类中声明为final的局部变量的值。不允许写作。这样做的理由是因为函数范围可能(实际上在这种情况下)退出,而变量可以从内部类中访问,该值实际上是在匿名类实例中缓存的。为了避免混淆并使JIT工作,这些变量只允许是最终的。请注意,只能修改原始值或引用,但仍可以修改引用对象中包含的任何内容。

在第二种情况下,它是线程可访问的实例变量。请注意,i = 1现在代表Main.this.i; Main.this.部分是隐含的。

答案 2 :(得分:6)

在第一个程序中,您可以“查看”变量i,但不能访问它,因为它未声明为final。只能访问在创建匿名类实例之前声明的成员变量和final局部变量:

public class Main {
    public static void main(String[] args) {
        final int i = 123; // Make the variable final
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(i); // Now it works, but you cannot assign it
            }
        }).start();
    }
}

Demo on ideone

使变量static也可以正常运行:

public class Main {
    private static int i = 321;
    public static void main (String[] args) throws java.lang.Exception
    {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(i); // Now it works, but you cannot assign it
            }
        }).start();
    }
}

Demo.