CompletableFuture递归循环未达40000次提前终止问题求助
CompletableFuture递归循环提前终止问题排查与解决
问题代码
private static void loop(Supplier<CompletionStage<Boolean>> body) { CompletionStage<Boolean> result = body.get(); Log.i(TAG, "after get"); result.whenComplete((r, e) -> { if (r) { Log.i(TAG, "call loop "); loop(body); } else { Log.i(TAG, "exit loop"); } }); } int cnt = 0; void test() { CompletableFuture<Void> supervisor = new CompletableFuture<>(); loop(() -> { Log.i(TAG, "cnt=" + cnt); if(cnt ++ < 40000) return CompletableFuture.completedFuture(true); else { return CompletableFuture.completedFuture(false); } }); Log.i(TAG, "leave"); }
执行输出
I/MainActivity: cnt=0
I/MainActivity: after get
I/MainActivity: call loop
I/MainActivity: cnt=1
I/MainActivity: after get
I/MainActivity: call loop
...
I/MainActivity: cnt=3073
I/MainActivity: after get
I/MainActivity: call loop
I/MainActivity: cnt=3074
I/MainActivity: after get
I/MainActivity: call loop
I/MainActivity: cnt=3075
I/MainActivity: after get
I/MainActivity: leave
原因分析
核心问题是栈溢出:
- 代码中
CompletableFuture.completedFuture()返回的是已完成的Future,whenComplete的回调会同步执行在当前调用线程中。 - 每次递归调用
loop()都会直接在当前栈帧上压入新的调用栈,随着循环次数增加,调用栈深度不断累积,当达到Android虚拟机的栈容量上限时,会抛出StackOverflowError,导致线程终止,循环提前结束。 - 由于Android Logcat未捕获该异常,所以表现为循环“提前结束”,实际是线程因栈溢出崩溃。
解决方案
要避免递归栈溢出,需让递归调用不在当前线程栈上同步执行,或改用非递归的链式调用逻辑。
方案1:异步调度递归调用
利用CompletableFuture的异步方法,将回调调度到通用线程池执行,避免栈累积:
private static void loop(Supplier<CompletionStage<Boolean>> body) { CompletionStage<Boolean> result = body.get(); Log.i(TAG, "after get"); // 使用thenRunAsync异步触发下一次循环,规避栈溢出 result.thenRunAsync(() -> { try { boolean r = result.toCompletableFuture().join(); if (r) { Log.i(TAG, "call loop "); loop(body); } else { Log.i(TAG, "exit loop"); } } catch (Exception e) { Log.e(TAG, "Loop error", e); } }); }
方案2:非递归链式调用(更推荐)
完全消除递归逻辑,用循环构建CompletableFuture链式调用,从根源上避免栈溢出:
void test() { CompletableFuture<Boolean> currentFuture = CompletableFuture.completedFuture(true); while (cnt < 40000) { currentFuture = currentFuture.thenApply(r -> { if (!r) return false; Log.i(TAG, "cnt=" + cnt); return cnt++ < 40000; }); } currentFuture.thenRun(() -> Log.i(TAG, "exit loop")); Log.i(TAG, "leave"); }
内容的提问来源于stack exchange,提问作者lucas
相关产品推荐
相关产品推荐

