Consider the following code:
import java.util.concurrent.Callable;
final public class DelayedSet {
String Val = "Uninitialized";
public Callable<String> Makegetter(String val) {
this.Val = val;
return new Callable<String>() {
public String call() {
return DelayedSet.this.Val;
}
};
}
}
class Main {
public static void main(String[] args) {
DelayedSet x = new DelayedSet();
Callable<String> Foogetter = x.Makegetter("Initialized");
// Version 1
try {
System.out.println(Foogetter.call());
}
catch (Exception e) {
}
}
}
After running Main, "Initialized" is printed.
Now consider Variant A , where Foogetter
is passed to a new Thread. Will then Foogetter
also return "Initialized" or is it possible, due to an out-of-date cache condition, for Foogetter
to return "Uninitialized"?
Also consider Variant B , where we have three threads, T1
, T2
, and T3
. T1
, via futures, submits a Callable
to T2
, where T2
creates DelayedSet
, calls Makegetter
, and returns "Foogetter"
(in quotes since its technically anonymous) via the future back to T1
. T1
then takes this result (the "Foogetter"
), and submits another callable, this time to T3
, where T3
calls "Foogetter"
. For both variants, is it guaranteed that "Initialized" will be returned or can "Uninitialized" ever be returned?
To summarize in psuedocode:
T1:
futureT2 = executorService.submit(new Callable {
...
call() {
// Runs in T2
Foo = new DelayedSet;
return Foo.Makegetter("Initialized");
} ...
futureT3 = executorService.submit(futureT2.get());
print(futureT3.get());
Coming from this question , I get the impression that one would need to rely on synchronization events to piggy back on, such as a volatile or synchronized block. However, I'm trying to determine the special case to not require volatiles (even via piggybacking), but due to happen-before semantics of thread creation and joining, will not incur any out-of-date cache conditions.
Can anyone clarify what the memory model with regard to threading is, in order to answer the question?
For Variant A , I'm going to assume something like
new Thread(() -> {
try {
System.out.println(Foogetter.call());
} catch (Exception e) {
}
}).start();
In this case, the JLS has us covered
A call to start() on a thread happens-before any actions in the started thread.
The
this.Val = val;
happened within the Makegetter
invocation which happens-before the invocation of Thread#start()
which then happens before the invocation of call
within the started thread. The value returned will always have to be "Initialized"
.
In Variant B , the first thing to note is the memory consistency effect of Future
Actions taken by the asynchronous computation happen-before actions following the corresponding
Future.get()
in another thread.
By the time futureT2.get()
returns in T1
, the call
invocation in T2
has happened(-before) and the invocation of MakeGetter
has already set the value of DelayedSet.Val
. This change is visible to T1
which gets the Callable
and to T3
which returns this updated value and to T1
again which retrieves it with futureT3.get()
.
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.