简体   繁体   中英

non-final non-local variable inside anonymous class

As I understand it, Java does not have true closures. You can pass a function by chaperoning them with a class; however, not only is it verbose but also (because of Java's memory model) any references in the anonymous class to variables defined in the environment where it was constructed are passed as copies . The language encourages us to remember this by only allowing anonymous classes to refer to final variables.

Which brings me to this code snippet I found in Bloch's 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;
    }
}

First, I expected the compiler to complain because stopRequested is nonfinal and I refer to it inside the anonymous class. My compiler didn't complain.

Second, I expected the program to loop forever since, well, Java doesn't support closures and if the anonymous class really is referring to the actual stopRequested variable from the environment it was constructed (and not a simple copy) then it seems like we have a closure here. Joshua Bloch also said the program loops forever on his computer. But mine runs for about a second and exits.

What part of the memory model am I misunderstanding?

The key thing you're missing is that the anonymous class is a nested class. As such, it has an implicit reference to the instance of the containing class, and therefore members of the class.

It is only local variables which are required to be final for use by anonymous classes.

It loops for me and the reason is to do with CPU cache, not anonymous methods.

With this it always exits:

private volatile static boolean stopRequested;

First, I expected the compiler to complain because stopRequested is nonfinal and I refer to it inside the anonymous class. My compiler didn't complain.

stopRequested is a static variable.

Second, I expected the program to loop forever since, well, Java doesn't support closures and if the anonymous class really is referring to the actual stopRequested variable from the environment it was constructed (and not a simple copy) then it seems like we have a closure here. Joshua Bloch also said the program loops forever on his computer. But mine runs for about a second and exits

stopRequested is not a volatile variable. Therefore, it may run forever( flag hoisting optimization . (run with -server mode) ).

Therefore, the below code

     while (!stopRequested)     
       i++;

can be reordered as

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

What you're misunderstanding is that only local variables are passed as copies to an inner class, because those exist on the stack and thus will be gone when the method call returns.

True closures "magically" provide a context for all captured variables to survive. But for non-local variables, this is not really necessary; they exist on the heap as part of their object or class, so Java permits them to be non-final and still used in an inner class.

What I think Bloch's code example is supposed to demonstrate is a completely different thing: different threads may have local copies of any variable (local, instance, or static) in their CPU's cache, and changes one thread makes may not be visible to other threads for an arbitrarily long time. To ensure that local copies are synced, the change either has to happen in a synchronized block/method, or the variable has to be declared volatile .

You can access fields via a reference (implicitly to OuterClass.this ) or static field by class. It is only non-final variables you cannot reference. Note: If this final reference points to something mutable you can change it.

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 is not a local variable it is a static variable so it need not be final. The program may loop forever because stopRequested is not declared volatile and thus it is not guaranteed that changes to stopRequested made by one thread will ever be seen in another thread. If you declare stopRequested volatile the program will not run forever.

Expecting the compiler to complain in this example is unusual. Usual expectation is that the program will terminate soon after start. Bloch shows that it may not be the case (which usually astonishes the reader) and then explains why. Bloch is a fairly advanced reading, you may want to try other books on Java first.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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