匿名类中的非最终非局部变量

时间:2011-11-14 10:22:09

标签: java closures anonymous-class

据我了解,Java没有真正的闭包。你可以通过chaperoning将一个函数传递给一个类;但是,它不仅冗长而且(由于Java的内存模型)匿名类中对构造它的环境中定义的变量的任何引用都作为副本传递。该语言鼓励我们通过仅允许匿名类引用final变量来记住这一点。

这让我看到了我在Bloch的 Effective Java 中找到的代码片段:

import java.util.concurrent.*;
public class StopThread {
    private static boolean stopRequested;

    public static void main(String[] args)
                    throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

首先,我期望编译器抱怨,因为stopRequested是非最终的,我在匿名类中引用它。我的编译器没有抱怨。

其次,我期望程序永远循环,因为Java不支持闭包,并且如果匿名类实际上是指它构造的环境中的实际stopRequested变量(而不是简单的)复制)那么看起来我们这里有一个闭包。 Joshua Bloch还表示,该程序永远在他的计算机上循环。但我的运行大约一秒钟就退出了。

我误解了记忆模型的哪个部分?

6 个答案:

答案 0 :(得分:3)

您缺少的关键是匿名类是嵌套类。因此,它具有对包含类的实例的隐式引用,因此具有类的成员。

只有局部变量需要final才能供匿名类使用。

答案 1 :(得分:1)

它为我循环,原因是CPU缓存,而不是匿名方法。

有了它,它总是会退出:

private volatile static boolean stopRequested;

答案 2 :(得分:1)

  

首先,我期望编译器抱怨因为stopRequested是   非最终的,我在匿名类中引用它。我的编译器   没抱怨。

stopRequested static 变量。

  

其次,我希望程序永远循环,因为Java   不支持闭包,如果匿名类真的是   从环境中引用实际的stopRequested变量   是构建(而不是一个简单的副本)然后它似乎我们有一个   在这里关闭。约书亚布洛赫也表示,该计划永远在他的身上循环   电脑。但我的运行大约一秒钟然后退出

stopRequested不是 volatile 变量。因此,它可能会永远运行(标志提升优化。(以-server模式运行))。

因此,以下代码

     while (!stopRequested)     
       i++;

可以重新排序为

  boolean status = !stopRequested; 
  while(status)     
      i++;

答案 3 :(得分:1)

您误解的是,只有局部变量作为副本传递给内部类,因为它们存在于堆栈中,因此在方法调用返回时将消失。

真正的闭包“神奇地”为所有捕获的变量提供了生存的上下文。但对于非局部变量,这不是必需的;它们作为对象或类的一部分存在于堆中,因此Java允许它们是非final的,仍然在内部类中使用。

我认为 Bloch的代码示例应该演示是完全不同的事情:不同的线程可能在其CPU的缓存中具有任何变量(本地,实例或静态)的本地副本,并且更改一个线程可能在任意长时间内对其他线程不可见。为确保同步本地副本,更改必须在synchronized块/方法中进行,或者必须将变量声明为volatile

答案 4 :(得分:0)

您可以按类别通过引用(隐式地OuterClass.this)或静态字段访问字段。它只是你无法引用的非最终变量。注意:如果此最终引用指向可变的内容,则可以更改它。

final int[] i = { 0 };
new Thread(new Runnable() {
    public void run() {
        i[0] = 1;
    }
}).start();
while(i[0] == 0);
System.out.println("i= " + i[0]);

答案 5 :(得分:0)

stopRequested不是局部变量,它是一个静态变量,所以它不一定是最终的。程序可能会永远循环,因为stopRequested未声明volatile,因此无法保证在另一个线程中可以看到由一个线程所做的stopRequested更改。如果您声明stopRequested volatile,该程序将无法永久运行。

期望编译器在这个例子中抱怨是不寻常的。通常的期望是该计划将在开始后很快终止。布洛赫表明情况可能并非如此(这通常使读者感到惊讶)然后解释了原因。布洛赫是一个相当高级的阅读,你可能想先尝试其他关于Java的书。