简体   繁体   English

如何强制 CompletableFuture.thenApply() 在运行前一阶段的同一线程上运行?

[英]How to force CompletableFuture.thenApply() to run on the same thread that ran the previous stage?

Here's a short code version of the problem I'm facing:这是我面临的问题的简短代码版本:

public static void main(String[] args) {
    CompletableFuture.supplyAsync(() -> {
                /*
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ignored) {}
                */
                //System.out.println("supplyAsync: " + Thread.currentThread().getName());
                return 1;
            })
            .thenApply(i -> {
                System.out.println("apply: " + Thread.currentThread().getName());
                return i + 1;
            })
            .thenAccept((i) -> {
                System.out.println("accept: " + Thread.currentThread().getName());
                System.out.println("result: " + i);
            }).join();
}

This is the output that I get:这是我得到的 output:

apply: main
accept: main
result: 2

I'm surprised to see main there!我很惊讶在那里看到main I expected something like this which happens when I uncomment the Thread.sleep() call or even as much as uncomment the single sysout statement there:当我取消注释Thread.sleep()调用,甚至取消注释单个sysout语句时,我预计会发生这样的事情:

supplyAsync: ForkJoinPool.commonPool-worker-1
apply: ForkJoinPool.commonPool-worker-1
accept: ForkJoinPool.commonPool-worker-1
result: 2

I understand thenApplyAsync() will make sure it won't run on the main thread, but I want to avoid passing the data returned by the supplier from the thread that ran supplyAsync to the thread that's going to run thenApply and the other subsequent then s in the chain.我了解thenApplyAsync()将确保它不会在main线程上运行,但我想避免将供应商返回的数据从运行supplyAsync的线程传递到将要运行thenApply的线程和另一个后续then s在链中。

The method thenApply evaluates the function in the caller's thread because the future has been completed already. thenApply方法在调用者的线程中评估 function,因为未来已经完成。 Of course, when you insert a sleep into the supplier, the future has not been completed by the time, thenApply is called.当然,当你在supplier中插入一个sleep ,此时future还没有完成, thenApply就会调用Apply。 Even a print statement might slow down the supplier enough to have the main thread invoke thenApply and thenAccept first.即使是打印语句也可能会减慢供应商的速度,以使主线程首先调用thenApplythenAccept But this is not reliable behavior, you may get different results when running the code repeatedly.但这不是可靠的行为,重复运行代码时可能会得到不同的结果。

Not only does the future not remember which thread completed it, there is no way to tell an arbitrary thread to execute a particular code.未来不仅不记得哪个线程完成了它,也没有办法告诉任意线程执行特定代码。 The thread might be busy with something else, being entirely uncooperative, or even have terminated in the meanwhile.该线程可能正忙于其他事情,完全不合作,甚至在此期间已经终止。

Just consider只考虑

ExecutorService s = Executors.newSingleThreadExecutor();
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
    System.out.println("supplyAsync: " + Thread.currentThread().getName());
    return 1;
}, s);
s.shutdown();
s.awaitTermination(1, TimeUnit.DAYS);
cf.thenApply(i -> {
    System.out.println("apply: " + Thread.currentThread().getName());
    return i + 1;
})
.thenAccept((i) -> {
    System.out.println("accept: " + Thread.currentThread().getName());
    System.out.println("result: " + i);
}).join();

How could we expect the functions passed to thenApply and thenAccept to be executed in the already terminated pool's worker thread?我们怎么能期望传递给thenApplythenAccept的函数在已经终止的池的工作线程中执行?

We could also write我们也可以写

CompletableFuture<Integer> cf = new CompletableFuture<>();

Thread t = new Thread(() -> {
    System.out.println("completing: " + Thread.currentThread().getName());
    cf.complete(1);
});
t.start();
t.join();

System.out.println("completer: " + t.getName() + " " + t.getState());
cf.thenApply(i -> {
    System.out.println("apply: " + Thread.currentThread().getName());
    return i + 1;
})
.thenAccept((i) -> {
    System.out.println("accept: " + Thread.currentThread().getName());
    System.out.println("result: " + i);
}).join();

which will print something alike这将打印类似的东西

completing: Thread-0
completer: Thread-0 TERMINATED
apply: main
accept: main
result: 2

Obviously, we can't insist on this thread processing the subsequent stages.显然,我们不能坚持让这个线程处理后续阶段。

But even when the thread is a still alive worker thread of a pool, it doesn't know that it has completed a future nor has it a notion of “processing subsequent stages”.但是,即使线程是池中仍然活着的工作线程,它也不知道它已经完成了未来,也没有“处理后续阶段”的概念。 Following the Executor abstraction, it just has received an arbitrary Runnable from the queue and after processing it, it proceeds with its main loop, fetching the next Runnable from the queue.Executor抽象之后,它刚刚从队列中接收到一个任意的Runnable ,在处理它之后,它继续其主循环,从队列中获取下一个Runnable

So once the first future has been completed, the only way to tell it to do the work of completing other futures, is by enqueuing the tasks.因此,一旦第一个 future 完成,告诉它完成其他 future 的工作的唯一方法就是将任务排入队列。 This is what happens when using thenApplyAsync specifying the same pool or performing all actions with the …Async methods without an executor, ie using the default pool.当使用thenApplyAsync指定相同的池或使用…Async方法执行所有操作时会发生这种情况,而无需执行器,即使用默认池。

When you use a single threaded executor for all …Async methods, you can be sure that all actions are executed by the same thread, but they will still get through the pool's queue.当您对所有…Async方法使用单线程执行器时,您可以确保所有操作都由同一个线程执行,但它们仍将通过池的队列。 Since even then, it's the main thread actually enqueuing the dependent actions in case of an already completed future, a thread safe queue and hence, synchronization overhead, is unavoidable.从那时起,在已经完成的未来的情况下,实际上是主线程将相关操作排入队列,线程安全队列以及因此的同步开销是不可避免的。

But note that even if you manage to create the chain of dependent actions first, before a single worker thread processes them all sequentially, this overhead is still there.但请注意,即使您首先设法创建依赖操作链,在单个工作线程按顺序处理它们之前,这种开销仍然存在。 Each future's completion is done by storing the new state in a thread safe way, making the result potentially visible to all other threads, and atomically checking whether a concurrent completion (eg a cancelation) has happened in the meanwhile.每个未来的完成都是通过以线程安全的方式存储新的 state 来完成的,使结果可能对所有其他线程可见,并以原子方式检查同时是否发生了并发完成(例如取消)。 Then, the dependent action(s) chained by other threads will be fetched, of course, in a thread safe way, before they are executed.然后,由其他线程链接的相关操作将在执行之前以线程安全的方式被获取。

All these actions with synchronization semantics make it unlikely that there are benefits of processing the data by the same thread when having a chain of dependent CompletableFuture s.所有这些具有同步语义的操作使得在具有依赖链的CompletableFuture时不太可能有由同一线程处理数据的好处。

The only way to have an actual local processing potentially with performance benefits is by using进行可能具有性能优势的实际本地处理的唯一方法是使用

CompletableFuture.runAsync(() -> {
    System.out.println("supplyAsync: " + Thread.currentThread().getName());
    int i = 1;

    System.out.println("apply: " + Thread.currentThread().getName());
    i = i + 1;

    System.out.println("accept: " + Thread.currentThread().getName());
    System.out.println("result: " + i);
}).join();

Or, in other words, if you don't want detached processing, don't create detached processing stages in the first place.或者,换句话说,如果您不想要分离处理,请不要首先创建分离处理阶段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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