简体   繁体   中英

Are latest changes made to shared context object between consecutive java CompletionStages always visible to each thread executing the lambda

package org.stackoverflow.example;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MeetPlay {

    private static final class Context {
        List<String> data = new ArrayList<>();
    }

    public static void main(String[] args) {

        ExecutorService executor = Executors.newFixedThreadPool(
                Runtime.getRuntime().availableProcessors());

        Context context = new Context();
        CompletableFuture.completedFuture(context)
                .thenAcceptAsync(c -> context.data.add("1"), executor)
                .thenAcceptAsync(__ -> context.data.add("2"), executor)
                .thenAcceptAsync(__ -> {
                    if (context.data.contains("1") && context.data.contains("2")) {
                        System.out.println("Will that always be the case");
                    } else {
                        System.out.println("Or not");
                    }
                }, executor);
    }
}

My current understanding is that code above will always print: "Will that always be the case" no matter how many threads the executor uses, stages and items are involved, or how "complex" the Context object is (eg with more nested fields), due to the happens before guarantee between java completion stages.

A colleague at work argues that is not guaranteed and says he has empirically proven it, however, I haven't actually seen it with my own eyes. Until that happens, I was wondering what do you guys think and most importantly WHY . References will be greatly appreciated!

EDIT: question is regarding the memory visibility guarantees around the shared context and not whether each stage is executed after the previous completes.

You are right - these stages are executed sequentially. This can be demonstrated by running this code:

    CompletableFuture.completedFuture(context)
        .thenAcceptAsync(c -> {
            context.data.add("1");
            System.out.println("1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            }, executor)
        .thenAcceptAsync(__ -> {
            context.data.add("2");
            System.out.println("2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
        }, executor)
        .thenAcceptAsync(__ -> {
            if (context.data.contains("1") && context.data.contains("2")) {
                System.out.println("Will that always be the case");
            } else {
                System.out.println("Or not");
            }
        }, executor);

The result will be:

1
(pause 1 sec)
2
(pause 1 sec)
Will that always be the case

This happens because CompletionStage returned from thenAcceptAsync completes only after action finishes. Ie if action completes exceptionally so will CompletionStage. This behaviour is the same as for thenAccept. "Async" in thenAcceptAsync only means that action will be executed using another executor.

If you want parallel execution consider the following code:

    CompletableFuture<Context> f = CompletableFuture.completedFuture(context);
    f.thenAcceptAsync(c -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}
        context.data.add("1");
        System.out.println("1");
    }, executor);
    f.thenAcceptAsync(__ -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}
        context.data.add("2");
        System.out.println("2");
    }, executor);
    f.thenAcceptAsync(__ -> {
        if (context.data.contains("1") && context.data.contains("2")) {
            System.out.println("Will that always be the case");
        } else {
            System.out.println("Or not");
        }
    }, executor);

Here 3 stages added to the same stage, not one after another. There is no dependency between one stage's completion and beginning of another. So stages may execute in parallel. The result will be:

Or not
(pause 1 sec)
2
1

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