If the Java compiler simply makes obj into a member variable, the last call to doSomethingAsync will overwrite the prior values of obj, making prior invocations of the thread use a wrong value
No, this will not happen. The subsequent call to doSomethingAsync
cannot overwrite the obj
captured by previous invocations of doSomethingAsync
. This stands even if you remove the final keyword (assume java let you do it for just this time).
I think your question ultimately is about how closure works/is implemented in java. However, your code is not demonstrating the complication in the proper way because the code is not even trying to modify the variable obj
in the same lexical scope.
In a way Java is not really capturing the variable obj
, but its value. You could write the your code in a different way, and the overall effect is the same:
class YourThread extends Thread {
private Object param;
public YourThread (Object obj){
param = obj;
}
@Override
public void run(){
//do something with your param
}
}
and you no longer need the final keyword:
public void doSomethingAsync (Object obj){
Thread t = new YourThread (obj);
t.start();
}
Now, say you have two instances of YourThread created, how could the second instance modify what has been passed as parameter to the first instance?
Closure in Other Languages
In other languages, magical things can indeed happen, but to show it you need to write the code slightly different:
public void doSomethingAsync (Object obj){
//Here let's assume obj is not null
Thread thread = new Thread (){
@Override
public void run () { ... /*do something with obj*/ ... }
}
thread.start ();
obj = null;
}
This is not valid Java code, but in certain languages code like that is allowed. And the thread, when its run
method is executed, might see obj
as null
.
Similarly, in the below code (again, not valid in Java), thread2 could potentially impact thread1 if thread2 executes first and changes obj
in its run
method:
public void doSomethingAsync (Object obj){
Thread thread1 = new Thread (){
@Override
public void run () { ... /*do something with obj*/ ... }
}
thread1.start ();
Thread thread2 = new Thread (){
@Override
public void run () { ... /*do something with obj*/ ... }
}
thread2.start ();
}
Back to Java
The reason Java forces you to put a final
on obj
is that although Java's syntax looks extremely similar to the closure syntax used in other languages, it is not doing the same closure semantics. Knowing it is final, Java does not need to create capturing object (thus additional heap allocation), but use something similar to YourThread behind the scene. See this link for more details