在处理复杂任务依赖关系时,Java 提供了多种多线程实现方式。本文将以一个典型任务依赖图为例,对比分析结构化并发(Structured Concurrency)与传统 CountDownLatch 两种实现方式的优劣。
任务依赖图分析
以下面的任务依赖图为例:
A B
\ /
C
/ \
D E
| / \
| F G
\ | /
H
任务依赖关系如下:
- A 和 B 没有依赖,可以并行执行
- C 依赖 A 和 B
- D 和 E 依赖 C
- F 和 G 依赖 E
- H 依赖 D、F 和 G
传统 CountDownLatch 实现
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(8);
// 创建所有任务的 latch
CountDownLatch latchA = new CountDownLatch(1);
CountDownLatch latchB = new CountDownLatch(1);
CountDownLatch latchC = new CountDownLatch(1);
CountDownLatch latchD = new CountDownLatch(1);
CountDownLatch latchE = new CountDownLatch(1);
CountDownLatch latchF = new CountDownLatch(1);
CountDownLatch latchG = new CountDownLatch(1);
CountDownLatch latchH = new CountDownLatch(1);
// 任务A - 无依赖
executor.submit(() -> {
try {
runTask("A", 100);
latchA.countDown();
} catch (InterruptedException ex) {
}
});
// 任务B - 无依赖
executor.submit(() -> {
try {
runTask("B", 150);
latchB.countDown();
} catch (InterruptedException ex) {
}
});
// 任务C - 依赖A和B
executor.submit(() -> {
try {
latchA.await();
latchB.await();
runTask("C", 200);
latchC.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 任务D - 依赖C
executor.submit(() -> {
try {
latchC.await();
runTask("D", 500);
latchD.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 任务E - 依赖C
executor.submit(() -> {
try {
latchC.await();
runTask("E", 120);
latchE.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 任务F - 依赖E
executor.submit(() -> {
try {
latchE.await();
runTask("F", 80);
latchF.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 任务G - 依赖E
executor.submit(() -> {
try {
latchE.await();
runTask("G", 90);
latchG.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 任务H - 依赖D, F, G
executor.submit(() -> {
try {
latchD.await();
latchF.await();
latchG.await();
runTask("H", 50);
latchH.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 等待所有任务完成
latchH.await();
executor.shutdown();
System.out.println("All tasks completed successfully");
}
private static Void runTask(String name, long duration) throws InterruptedException {
System.out.println("Task " + name + " running");
Thread.sleep(duration);
System.out.println("Task " + name + " completed");
return null;
}
}
传统实现特点:
- 需要显式创建和管理多个 CountDownLatch
- 每个任务的依赖关系通过
await()方法显式声明 - 线程池需要手动管理生命周期
- 错误处理需要单独实现
结构化并发
结构化并发(Structured Concurrency)是 Java 19 引入的一种并发编程范式,旨在通过代码结构显式表达任务的生命周期和依赖关系,从而简化多线程开发。其核心思想是:子任务的执行必须在其父任务的上下文中完成,避免"线程泄漏"和复杂的同步控制。
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
public class StructuredConcurrency {
public static void main(String[] args) {
try {
// 阶段1: A和B并行
try (var scopeAB = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<Void> a = scopeAB.fork(() -> runTask("A", 100));
Subtask<Void> b = scopeAB.fork(() -> runTask("B", 150));
scopeAB.join().throwIfFailed();
// 阶段2: C执行
runTask("C", 200);
// 阶段3: D和E并行
try (var scopeDE = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<Void> d = scopeDE.fork(() -> runTask("D", 500));
// E完成后立即触发F和G,不需要等待D
Subtask<Void> e = scopeDE.fork(() -> {
runTask("E", 120);
// E完成后立即启动F和G
try (var scopeFG = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<Void> f = scopeFG.fork(() -> runTask("F", 80));
Subtask<Void> g = scopeFG.fork(() -> runTask("G", 90));
scopeFG.join().throwIfFailed();
}
return null;
});
scopeDE.join().throwIfFailed();
// 阶段4: H执行(需要D, F, G完成)
// 注意: F和G已经在E的任务中完成
runTask("H", 50);
}
}
System.out.println("All tasks completed successfully");
} catch (Exception e) {
System.err.println("Task failed: " + e.getMessage());
}
}
private static Void runTask(String name, long duration) throws InterruptedException {
System.out.println("Task " + name + " running");
Thread.sleep(duration);
System.out.println("Task " + name + " completed");
return null;
}
}
结论
对比两种实现,可以发现结构化并发的代码短很多。此外,结构化并发还有以下优势:
- 取消传播:父任务取消会自动传播到所有子任务
- 短路错误处理:如果某个子任务失败,其它子任务会自动被取消
- 逻辑直观:任务依赖关系通过代码结构自然表达,无需显式同步机制