简体   繁体   中英

Can a non-volatile variable that is delayed assigned to by a method of the class not be read by another thread?

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.

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