繁体   English   中英

匿名类中的非最终非本地变量

[英]non-final non-local variable inside anonymous class

据我了解,Java没有真正的闭包。 您可以通过对一个类进行陪伴来传递函数。 但是,它不仅冗长,而且(由于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)还说,该程序在他的计算机上永远循环。 但是我的跑了大约一秒钟然后退出了。

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

您缺少的关键是匿名类是嵌套类。 这样,它隐式地包含了包含类的实例,因此也引用了该类的成员。

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

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

与此总是退出:

private volatile static boolean stopRequested;

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

stopRequested是一个static变量。

其次,我希望程序能够永远循环下去,因为Java不支持闭包,并且如果匿名类确实是在构造它的环境(而不是简单的副本)中引用实际的stopRequested变量,那么我们似乎在这里关闭。 约书亚·布洛赫(Joshua Bloch)还说,该程序在他的计算机上永远循环。 但是我的跑了大约一秒钟然后退出

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

因此,下面的代码

     while (!stopRequested)     
       i++;

可以重新排序为

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

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

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

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

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

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

期望编译器在此示例中抱怨是不寻常的。 通常希望程序在启动后立即终止。 布洛赫(Bloch)指出事实并非如此(这通常会使读者感到惊讶),然后解释原因。 Bloch是一本相当高级的书籍,您可能想先尝试使用Java的其他书籍。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM