Java 为什么ThenComposeSync要等待回报才能赎回
我已经编写了一个人为的代码示例,它可能不是某个人应该使用的代码,但我相信它应该工作。然而,它却陷入了僵局。我已经阅读了描述的答案,但发现它们不够充分 下面是代码示例:Java 为什么ThenComposeSync要等待回报才能赎回,java,java-8,completable-future,Java,Java 8,Completable Future,我已经编写了一个人为的代码示例,它可能不是某个人应该使用的代码,但我相信它应该工作。然而,它却陷入了僵局。我已经阅读了描述的答案,但发现它们不够充分 下面是代码示例: import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class Test { public static vo
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class Test {
public static void main(String argv[]) throws Exception {
int nThreads = 1;
Executor executor = Executors.newFixedThreadPool( nThreads );
CompletableFuture.completedFuture(true)
.thenComposeAsync((unused)->{
System.err.println("About to enqueue task");
CompletableFuture<Boolean> innerFuture = new CompletableFuture<>();
executor.execute(() -> {
// pretend this is some really expensive computation done asynchronously
System.err.println("Inner task");
innerFuture.complete(true);
});
System.err.println("Task enqueued");
return innerFuture;
}, executor).get();
System.err.println("All done");
System.exit(0);
}
}
import java.util.concurrent.CompletableFuture;
导入java.util.concurrent.Executor;
导入java.util.concurrent.Executors;
公开课考试{
公共静态void main(字符串argv[])引发异常{
int=1;
Executor Executor=Executors.newFixedThreadPool(nThreads);
CompletableFuture.completedFuture(真)
.ThenComposeSync((未使用)->{
System.err.println(“即将排队任务”);
CompletableFuture innerFuture=新的CompletableFuture();
执行者。执行(()->{
//假设这是一个非常昂贵的异步计算
System.err.println(“内部任务”);
innerFuture.complete(true);
});
System.err.println(“任务排队”);
回归未来;
},执行器)。get();
系统错误打印项次(“全部完成”);
系统出口(0);
}
}
这张照片是:
即将使任务排队
任务排队
然后它就挂了。它处于死锁状态,因为执行器只有一个线程,它正在等待内部未来变得可赎回。为什么“ThenComposeSync”阻止其返回值成为可赎回的,而不是返回仍然不完整的未来并释放其在执行器中的线程
这感觉完全不直观,javadocs也没有真正的帮助。我从根本上误解了完成阶段的工作方式吗?或者这是实现中的一个bug?首先,让我用两个静态函数重写您的代码,以便更容易看到发生了什么:
// Make an executor equivalent to Executors.newFixedThreadPool(nThreads)
// that will trace to standard error when a task begins or ends
static ExecutorService loggingExecutor(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()) {
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.err.println("Executor beginning task on thread: "
+ t.getName());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.err.println("Executor finishing task on thread: "
+ Thread.currentThread().getName());
}
};
}
让我们测试您的结论,即在计算第二个未来的结果之前,不会释放第一个线程:
ExecutorService e = loggingExecutor(2); // use 2 threads this time
CompletableFuture.completedFuture(true)
.thenComposeAsync(inner(e), e)
.join();
e.shutdown();
/*
Executor beginning task on thread: pool-1-thread-1
pool-1-thread-1: About to enqueue task
pool-1-thread-1: Task enqueued
Executor beginning task on thread: pool-1-thread-2
pool-1-thread-2: Inner task
Executor finishing task on thread: pool-1-thread-2
Executor finishing task on thread: pool-1-thread-1
*/
实际上,线程1似乎一直保持到线程2完成为止
让我们看看thencoseasync
本身是否阻止了以下内容:
ExecutorService e = loggingExecutor(1);
CompletableFuture<Boolean> future =
CompletableFuture.completedFuture(true)
.thenComposeAsync(inner(e), e);
System.err.println("thenComposeAsync returned");
future.join();
e.shutdown();
/*
thenComposeAsync returned
Executor beginning task on thread: pool-1-thread-1
pool-1-thread-1: About to enqueue task
pool-1-thread-1: Task enqueued
*/
如您所见,.thenRun(…)已在线程1上执行。我相信这与CompletableFuture的其他*Async(…,Executor-exec)方法是一致的
但是,如果您想将thencomposesync
的功能分为两个单独可控的步骤,而不是让API来处理线程,该怎么办?您可以这样做:
ExecutorService e = loggingExecutor(1);
completedFuture(true)
.thenApplyAsync(inner(e), e) // do the async part first
.thenCompose(x -> x) // compose separately
.thenRun(() -> System.err.println(Thread.currentThread().getName()
+ ": All done"))
.join();
e.shutdown();
一切都将在1个线程上运行良好,没有死锁
总之,这种行为是否如你所说的那样不直观?我不知道。我无法想象为什么
thencoseasync
甚至存在。如果一个方法返回CompletableFuture
,它不应该阻塞,也没有理由异步调用它 所以,在进行了很多有趣的对话之后,我决定给JDK的一位作者发电子邮件。发现这种行为不是故意的,实际上是1.8u25中存在的一个bug。有一个修补程序将与更高版本的Java 8一起发布。我不知道是哪个。对于想要测试新行为的任何人,可以在此处下载最新的jsr166 jar:
感谢米莎提供的细节和努力。你的例子比我的清楚得多。我需要考虑一下你的一些陈述。具体地说:“1.API需要等待Inner(e)返回CompletableFuture;2.它需要等待返回的CompletableFuture也完成。只有这样,未来才是完整的。因此,正如你所看到的,它不能按照你的建议返回不完整的未来。”我并不是在做“正如你所看到的……”所暗示的精神飞跃我会想一想,等我想好了再发回来。我不知道。不过,到现在为止,这个问题应该已经解决很久了。你看到了吗?我以为我看到了,但结果却是用户代码中一个无关的bug。无论如何,谢谢你。
ExecutorService e = loggingExecutor(1);
CompletableFuture<Boolean> future =
CompletableFuture.completedFuture(true)
.thenComposeAsync(inner(e), e);
System.err.println("thenComposeAsync returned");
future.join();
e.shutdown();
/*
thenComposeAsync returned
Executor beginning task on thread: pool-1-thread-1
pool-1-thread-1: About to enqueue task
pool-1-thread-1: Task enqueued
*/
ExecutorService e = loggingExecutor(2);
CompletableFuture.completedFuture(true)
.thenComposeAsync(inner(e), e)
.thenRun(() -> System.err.println(Thread.currentThread().getName()
+ ": All done"))
.join();
e.shutdown();
/*
Executor beginning task on thread: pool-1-thread-1
pool-1-thread-1: About to enqueue task
pool-1-thread-1: Task enqueued
Executor beginning task on thread: pool-1-thread-2
pool-1-thread-2: Inner task
Executor finishing task on thread: pool-1-thread-2
pool-1-thread-1: All done
Executor finishing task on thread: pool-1-thread-1
*/
ExecutorService e = loggingExecutor(1);
completedFuture(true)
.thenApplyAsync(inner(e), e) // do the async part first
.thenCompose(x -> x) // compose separately
.thenRun(() -> System.err.println(Thread.currentThread().getName()
+ ": All done"))
.join();
e.shutdown();