Return 一个不暴露执行线程的 CompletableFuture
Return a CompletableFuture without exposing executor thread
我在库中公开了一个方法,returns 一个 CompletableFuture。该方法的计算发生在单线程执行器上,这是我的瓶颈,因此我不希望任何后续工作发生在同一线程上。
如果我使用返回 "supplyAsync" 结果的简单方法,我会将我宝贵的线程暴露给调用者,调用者可能正在添加同步操作(例如通过 thenAccept),这可能需要一些 CPU 时间在那个线程上。
重现如下:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CfPlayground {
private ExecutorService preciousExecService = Executors.newFixedThreadPool(1);
CfPlayground() {}
private static void log(String msg) {
System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
}
CompletableFuture<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService);
}
void syncOp(String salutation) {
log("In syncOp: " + salutation);
}
void run() {
log("run");
asyncOp("world").thenAccept(this::syncOp);
}
public static void main(String[] args) throws InterruptedException {
CfPlayground compFuture = new CfPlayground();
compFuture.run();
Thread.sleep(500);
compFuture.preciousExecService.shutdown();
}
}
这确实打印:
[main] run
[pool-1-thread-1] In asyncOp
[pool-1-thread-1] In syncOp: Hello world
我找到的一个解决方案是引入另一个执行器,并在返回 CompletableFuture 之前添加一个空操作 thenApplyAsync 与该执行器
CompletableFuture<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService).thenApplyAsync(s -> s, secondExecService);
}
这可行,但感觉不是很优雅 - 有更好的方法吗?
您可以将方法签名更改为 return a Future
而不是 CompletableFuture
:
Future<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService);
}
这样,run()
方法会抛出编译错误:
void run() {
log("run");
asyncOp("world").thenAccept(this::syncOp);
}
来电者仍然可以将 returned Future
转换回 CompletableFuture
,但这将是对您的 API 的滥用,并且它不可能偶然发生。
没有将您的完成与相关操作的执行分离的功能。当链接依赖动作的线程已经完成注册并且你的执行者的线程完成未来时,如果没有其他执行者被指定,哪个其他线程应该执行依赖动作?
你将另一个动作与不同的执行者链接起来的方法似乎是你能得到的最好的方法。然而,重要的是要注意,在异常完成的情况下,异常会在不评估传递给 thenApply
的函数的情况下传播。如果调用者链接了 whenComplete
、handle
或 exceptionally
.
等操作,则此异常传播可能再次导致线程暴露
另一方面,您不需要指定辅助执行程序,因为您可以使用不带执行程序参数的 async 方法来获取默认值(常见 Fork/Join) 池。
因此,链接 .whenCompleteAsync((x,y) -> {})
是迄今为止解决您的问题的最佳方法。
我在库中公开了一个方法,returns 一个 CompletableFuture。该方法的计算发生在单线程执行器上,这是我的瓶颈,因此我不希望任何后续工作发生在同一线程上。
如果我使用返回 "supplyAsync" 结果的简单方法,我会将我宝贵的线程暴露给调用者,调用者可能正在添加同步操作(例如通过 thenAccept),这可能需要一些 CPU 时间在那个线程上。
重现如下:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CfPlayground {
private ExecutorService preciousExecService = Executors.newFixedThreadPool(1);
CfPlayground() {}
private static void log(String msg) {
System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
}
CompletableFuture<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService);
}
void syncOp(String salutation) {
log("In syncOp: " + salutation);
}
void run() {
log("run");
asyncOp("world").thenAccept(this::syncOp);
}
public static void main(String[] args) throws InterruptedException {
CfPlayground compFuture = new CfPlayground();
compFuture.run();
Thread.sleep(500);
compFuture.preciousExecService.shutdown();
}
}
这确实打印:
[main] run
[pool-1-thread-1] In asyncOp
[pool-1-thread-1] In syncOp: Hello world
我找到的一个解决方案是引入另一个执行器,并在返回 CompletableFuture 之前添加一个空操作 thenApplyAsync 与该执行器
CompletableFuture<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService).thenApplyAsync(s -> s, secondExecService);
}
这可行,但感觉不是很优雅 - 有更好的方法吗?
您可以将方法签名更改为 return a Future
而不是 CompletableFuture
:
Future<String> asyncOp(String param) {
return CompletableFuture.supplyAsync(() -> {
log("In asyncOp");
return "Hello " + param;
}, preciousExecService);
}
这样,run()
方法会抛出编译错误:
void run() {
log("run");
asyncOp("world").thenAccept(this::syncOp);
}
来电者仍然可以将 returned Future
转换回 CompletableFuture
,但这将是对您的 API 的滥用,并且它不可能偶然发生。
没有将您的完成与相关操作的执行分离的功能。当链接依赖动作的线程已经完成注册并且你的执行者的线程完成未来时,如果没有其他执行者被指定,哪个其他线程应该执行依赖动作?
你将另一个动作与不同的执行者链接起来的方法似乎是你能得到的最好的方法。然而,重要的是要注意,在异常完成的情况下,异常会在不评估传递给 thenApply
的函数的情况下传播。如果调用者链接了 whenComplete
、handle
或 exceptionally
.
另一方面,您不需要指定辅助执行程序,因为您可以使用不带执行程序参数的 async 方法来获取默认值(常见 Fork/Join) 池。
因此,链接 .whenCompleteAsync((x,y) -> {})
是迄今为止解决您的问题的最佳方法。