Even if run() is used which doesn't create new threads, it doesn't compile, neither will any other lambda expression. This makes sense since you could execute lambda expressions multiple times, which would mean trying to reassign a final variable.
Inside a lambda expression, you can initialize variables in the enclosing code. You can use only effectively final variables inside lambda expressions. It's not only related to lambdas, the same applies to anonymous classes and that restriction exists from the earlier days of Java. Java 8 only brought us the notion of effectively final variables.
Here's a simplified answer:
I guess you know that there are two memory areas: the heap where all objects reside and a stack in which is meant to keep the data related to each method call.
Variables related to a particular method call form a layer of the stack, i.e. when a method is being called a new layer gets allocated on the top of the stack, when it calls another method - another layer gets allocated on top of this layer. Even when a variable is being passed around between different layers, each stack layer has its own version of this variable and their values are completely independent. When a method terminates all its layer is being deallocated, and we're loosing access to all its variables, the layer beneath becomes active.
Lambda as well as an instance of the anonymous class are objects that reside in the heap. If there's at least one reference that leads to them on any of the layers of the stack, they would be alive and can sustain much longer than variables of the layer where they have been created. Therefore, it would have no sense if we imagine that it would be allowed to change a local variable from an anonymous class or lambda expression because there's no guarantee that these variables would exist at the moment.
When a lambda or anonymous class is being created, JVM creates a copy of everything that is accessible. If a local variable has not been initialized, it would not be captured because it's not effectively final.
You can find a very simple definition of effectively final variable at the very end of the paragraph 4.12.4. of the Java language specification
If a variable is effectively final, adding the final
modifier to its declaration will not introduce any compile-time errors. Conversely, a local variable or parameter that is declared final in a valid program becomes effectively final if the final modifier is removed.
If variable isn't initialized at neither at the moment of declaration, no anywhere in the code, then if you would try to add final
modifier to its declaration, it'll cause a compilation. Such variable is not effectively final.
The only solution I know is to define the variable as a one-element array, which allows you to change the element.
If a reference to the object doesn't get changed (i.e. a local variable pointing to the object doesn't change after initialization) we can use this object inside the lambda expression. While creating a lambda, JVM will capture a reference to such object (note: since it's an object, no copies would be created, the JVM would capture the reference to the object itself), and this object would be alive at least while the object representing a lambda is alive. In case when object is mutable (array, collection, etc.) we can modify it, and these changes can be observed in other peaces of the code that possess the reference to the same object (but it's not guaranteed if a non-thread-safe object is being accessed concurrently).
Also note that if a non-synchronized object like an array is being simultaneously modified and accessed for reading from different threads, there's no guaranty the data that has been read is correct. For such cases, have a look at the classes from the package java.util.concurrent.atomic
.