【问题标题】:non-final non-local variable inside anonymous class匿名类中的非最终非局部变量
【发布时间】:2011-11-14 10:22:08
【问题描述】:

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

这让我想到了我在 Bloch 的 Effective Java 中找到的这段代码 sn-p:

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 还说程序在他的计算机上永远循环。但我的运行大约一秒钟然后退出。

我误解了内存模型的哪一部分?

【问题讨论】:

    标签: java closures anonymous-class


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

      这样它总是退出:

      private volatile static boolean stopRequested;
      

      【讨论】:

        【解决方案3】:

        首先,我希望编译器会抱怨,因为 stopRequested 是 nonfinal,我在匿名类中引用它。我的编译器 没有抱怨。

        stopRequested 是一个 static 变量。

        其次,我希望程序永远循环,好吧,Java 不支持闭包,如果匿名类真的是 从它的环境中引用实际的 stopRequested 变量 被构造(而不是简单的副本)然后看起来我们有一个 在这里关闭。 Joshua Bloch 还说程序在他的 计算机。但是我的运行了大约一秒钟就退出了

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

        因此,下面的代码

             while (!stopRequested)     
               i++;
        

        可以重新排序为

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

        【讨论】:

          【解决方案4】:

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

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

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

          【讨论】:

          • 你说的完全正确。 Bloch 试图理解一个不相关的概念,但引起我注意的是“什么?Java 有闭包???从什么时候开始???”谢谢你。那么看起来Java - 几乎 - 有闭包?匿名类从类或对象构造时是闭包,但在函数中构造时不是?
          • @highwind7777:匿名或本地类的行为总是与几乎闭包一样。不同的是本地变量与实例或静态变量的行为与其上下文:局部变量被复制,因此必须是最终的。实例变量通过对其封闭对象的隐藏引用进行访问。
          【解决方案5】:

          您可以通过引用(隐式指向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]);
          

          【讨论】:

            【解决方案6】:

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

            在这个例子中期望编译器抱怨是不寻常的。通常的期望是程序将在启动后不久终止。布洛赫表明情况可能并非如此(这通常会让读者感到惊讶),然后解释了原因。 Bloch 是一本相当高级的读物,您可能想先尝试其他有关 Java 的书籍。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-13
              • 1970-01-01
              • 1970-01-01
              • 2019-03-16
              • 1970-01-01
              相关资源
              最近更新 更多