Structured Concurrency — StructuredTaskScope Java 25
Structured Concurrency từ preview (JEP 428, 453, 462, 480, 499) tới GA Java 25 (JEP 505). StructuredTaskScope, ShutdownOnFailure/Success, lifecycle tự động, cancellation propagation, và so sánh với CompletableFuture.
TL;DR: Structured Concurrency (GA Java 25, JEP 505) áp dụng nguyên tắc structured programming vào concurrency: task con không được sống lâu hơn task cha. StructuredTaskScope đảm bảo nếu scope thoát (kể cả do exception), tất cả subtask bị cancel và join. ShutdownOnFailure dừng toàn bộ khi 1 task fail. ShutdownOnSuccess trả kết quả nhanh nhất. So với CompletableFuture.allOf(), Structured Concurrency tự động cancel, error propagation rõ ràng, và code sequential-style đọc dễ hơn nhiều. Thiết kế tối ưu cho Virtual Thread (bài 05) — scale tới triệu subtask không tốn OS thread.
Bài này build-on bài 05 (Virtual Thread), bài 07 (CompletableFuture), và bài 06 (ReentrantLock) — đọc 3 bài đó trước. Đặc biệt bài 07 mục 7 về allOf — những giới hạn ở đó là motivation trực tiếp cho Structured Concurrency.
1. Scenario — resource leak với CompletableFuture
Service cần lấy 3 piece of data song song: user profile, active orders, personalized recommendations. Dùng CompletableFuture.allOf():
public ProfileView getProfile(long userId) {
ExecutorService pool = Executors.newVirtualThreadPerTaskExecutor();
CompletableFuture<User> userCF = CompletableFuture.supplyAsync(() -> fetchUser(userId), pool);
CompletableFuture<List<Order>> orderCF = CompletableFuture.supplyAsync(() -> fetchOrders(userId), pool);
CompletableFuture<List<Item>> recCF = CompletableFuture.supplyAsync(() -> recommend(userId), pool);
CompletableFuture.allOf(userCF, orderCF, recCF).join(); // wait all
return new ProfileView(userCF.join(), orderCF.join(), recCF.join());
}
Bài toán: nếu fetchUser() ném exception sau 50ms, allOf fail ngay — nhưng fetchOrders() và recommend() tiếp tục chạy đến khi kết thúc, tốn resource vô ích.
Hơn nữa: nếu method caller timeout và cancel, làm sao propagate cancel xuống 3 CF con? Không có API tự động — phải track và cancel() thủ công từng CF. Và như đã thấy ở bài 07, CompletableFuture.cancel() không thực sự interrupt computation.
Structured Concurrency giải quyết: scope fail → tất cả subtask bị interrupt và join trước khi scope đóng. Không resource leak. Không manual cancel tracking.
2. Định nghĩa — structured concurrency là gì và vì sao tồn tại
Structured concurrency (thuật ngữ do Nathaniel J. Smith giới thiệu 2016, sau đó Ron Pressler của Oracle áp vào Project Loom) áp nguyên tắc structured programming vào concurrent code:
Nếu task A khởi động task B, A không được kết thúc trước khi B kết thúc.
Giống như trong structured programming: nếu function A gọi function B, A không return trước khi B return. Call stack đảm bảo điều này tự động. Structured Concurrency mang đảm bảo tương tự vào concurrent task.
Lịch sử JEP:
- JEP 428 (Java 19 incubator):
StructuredTaskScopetrongjdk.incubator.concurrent. - JEP 437 (Java 20 incubator): tiếp tục incubation, ít thay đổi.
- JEP 453 (Java 21 preview): chuyển sang
java.util.concurrent, thêmSubtask<T>. - JEP 462 (Java 21 second preview): refinement,
Subtask.state(). - JEP 480 (Java 23 preview): API ổn định hơn.
- JEP 499 (Java 24 preview): cleanup cuối.
- JEP 505 (Java 25): GA — finalized. Không cần
--enable-preview.
Hôm nay (2026-05-08), Java 25 đã GA (tháng 9/2025) — Structured Concurrency production-ready.
3. Unstructured vs Structured — so sánh cơ chế
Unstructured (CompletableFuture / Future):
Main task
├── CF A (submit to pool) ──────────────────── runs independently
├── CF B (submit to pool) ──────────────────── runs independently
└── CF C (submit to pool) ──────────────────── runs independently
^
Nếu main fail, A/B/C vẫn chạy
Task con tồn tại độc lập với main. Lifecycle không liên kết. Cancellation không tự động propagate. Exception từ 1 task không tự cancel tasks còn lại.
Structured (StructuredTaskScope):
Main task (scope owner)
├── scope.fork(A) ─── Subtask A
├── scope.fork(B) ─── Subtask B
└── scope.fork(C) ─── Subtask C
scope.join() ← wait all A, B, C
// scope.close() → nếu scope thoát, cancel bất kỳ subtask còn chạy
Tất cả subtask được join trước khi scope đóng. Nếu scope thoát do exception hoặc timeout, JVM interrupt tất cả subtask chưa xong. Task con không thể "sống lâu hơn" task cha.
4. API cơ bản — StructuredTaskScope
StructuredTaskScope<T> là abstract class trong java.util.concurrent. Lifecycle qua try-with-resources:
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<User> userTask = scope.fork(() -> fetchUser(id));
Subtask<List<Order>> orderTask = scope.fork(() -> fetchOrders(id));
Subtask<List<Item>> recTask = scope.fork(() -> recommend(id));
scope.join(); // wait for all subtasks to complete
scope.throwIfFailed(); // rethrow if any subtask failed
// All succeeded -- access results
return new ProfileView(
userTask.get(),
orderTask.get(),
recTask.get()
);
} // scope.close() auto-cancels any still-running subtasks
Giải thích từng bước:
new StructuredTaskScope.ShutdownOnFailure()— tạo scope với policy "nếu 1 subtask fail, cancel tất cả còn lại".scope.fork(callable)— start subtask trên 1 Virtual Thread mới trong scope. Trả vềSubtask<T>(handle). Subtask bắt đầu chạy ngay.scope.join()— block scope owner thread cho đến khi tất cả subtask hoàn thành (hoặc scope shutdown do policy).scope.throwIfFailed()— nếu bất kỳ subtask nào ném exception, rethrow ở đây (wrapped trong exception phù hợp). Nếu không có failure, no-op.subtask.get()— lấy kết quả subtask đã hoàn thành. Chỉ gọi saujoin(). Nếu subtask fail, throw exception. Nếu subtask bị cancel, throwCancellationException.scope.close()(tự động qua try-with-resources) — cancel bất kỳ subtask chưa xong, đợi chúng kết thúc, đóng scope.
Subtask state
Subtask.State state = userTask.state();
// RUNNING -- subtask dang chay
// SUCCESS -- hoan thanh, userTask.get() tra ket qua
// FAILED -- nen exception, userTask.exception() tra throwable
// UNAVAILABLE -- chua fork hoac scope chua join
5. Policy ShutdownOnFailure — fail fast
ShutdownOnFailure (inner class của StructuredTaskScope): khi bất kỳ subtask nào fail (ném exception), scope shutdown — cancel interrupt tất cả subtask còn đang chạy. join() trả ngay khi tất cả hoàn thành (cancelled hoặc thành công).
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<String> a = scope.fork(() -> callServiceA());
Subtask<String> b = scope.fork(() -> callServiceB());
Subtask<String> c = scope.fork(() -> callServiceC()); // throws after 100ms
scope.join(); // returns after C fails + A, B cancelled
scope.throwIfFailed(); // rethrows C's exception
return a.get() + b.get(); // never reached if C failed
}
Kịch bản: A cần 500ms, B cần 300ms, C fail sau 100ms. Với ShutdownOnFailure:
- 100ms: C fail → scope shutdown → A và B bị interrupt.
- A và B nhận
InterruptedException(hoặc IO interrupted) và kết thúc. join()trả khi A, B đã kết thúc (nhanh vì interrupt).throwIfFailed()rethrow exception của C.- Tổng thời gian: ~100ms + overhead. Không phải 500ms.
Use case: tất cả subtask phải thành công. Fail nhanh khi có lỗi.
6. Policy ShutdownOnSuccess — first result wins
ShutdownOnSuccess<T>: khi bất kỳ subtask nào thành công, scope shutdown — cancel interrupt subtasks còn lại.
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
scope.fork(() -> primaryDb.query(id)); // 200ms nếu primary up
scope.fork(() -> replicaDb.query(id)); // 250ms nếu replica up
scope.fork(() -> cacheLayer.get(id)); // 50ms nếu cache hit
scope.join();
return scope.result(); // result of the first successful subtask
}
Kịch bản: cache hit sau 50ms → scope shutdown → primary và replica query bị cancel. scope.result() trả giá trị từ cache subtask. Total latency: ~50ms thay vì 200ms.
Use case: redundant lookup — nhiều source trả cùng data, lấy kết quả nhanh nhất. Hedged request pattern.
Nếu tất cả subtask fail, scope.result() throw ExecutionException wrapping exception của subtask cuối fail.
// Xu ly khi tat ca fail
try {
return scope.result();
} catch (ExecutionException ex) {
throw new ServiceUnavailableException("All sources failed", ex.getCause());
}
7. Custom policy — extend StructuredTaskScope
Custom policy bằng cách override handleComplete(Subtask<? extends T> subtask):
// Policy: majority vote -- ket thuc khi co nhieu hon N/2 ket qua giong nhau
class MajorityVoteScope<T> extends StructuredTaskScope<T> {
private final List<T> results = new CopyOnWriteArrayList<>();
private final int threshold;
private volatile T majorityResult;
MajorityVoteScope(int numTasks) {
this.threshold = numTasks / 2 + 1;
}
@Override
protected void handleComplete(Subtask<? extends T> subtask) {
if (subtask.state() == Subtask.State.SUCCESS) {
T val = subtask.get();
results.add(val);
// Count occurrences
long count = results.stream().filter(val::equals).count();
if (count >= threshold) {
majorityResult = val;
shutdown(); // cancel remaining subtasks
}
}
}
public T majorityResult() {
if (majorityResult == null) throw new IllegalStateException("No majority");
return majorityResult;
}
}
Sử dụng:
try (var scope = new MajorityVoteScope<String>(3)) {
scope.fork(() -> node1.vote(proposal));
scope.fork(() -> node2.vote(proposal));
scope.fork(() -> node3.vote(proposal));
scope.join();
return scope.majorityResult();
}
handleComplete được gọi mỗi khi 1 subtask hoàn thành (success, failure, hoặc cancelled) — trên subtask thread. Thread-safe access bắt buộc. shutdown() method bảo scope cancel và kết thúc join.
8. Integration với Virtual Thread
Structured Concurrency được thiết kế cùng với Virtual Thread (bài 05) — chúng hoàn toàn bổ sung nhau.
scope.fork() mặc định tạo Virtual Thread cho mỗi subtask. Không phải platform thread, không pool, không size limit:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 10000 HTTP calls -- moi call la 1 virtual thread
List<Subtask<String>> tasks = new ArrayList<>();
for (String url : urls) {
tasks.add(scope.fork(() -> httpGet(url)));
}
scope.join();
scope.throwIfFailed();
return tasks.stream().map(Subtask::get).toList();
}
10000 subtask = 10000 Virtual Thread. Khi mỗi thread block I/O (HTTP), JVM unmount khỏi carrier — carrier free xử lý thread khác. Effective concurrency cao mà không block OS thread. Carrier pool vẫn nhỏ (= số CPU core).
Cancellation qua interrupt: khi scope shutdown (do policy hoặc exception), JVM gọi Thread.interrupt() trên tất cả virtual thread đang chạy trong scope. Virtual Thread respond nhanh vì:
Thread.sleep(),socket.read(),Lock.lockInterruptibly()— tất cả check interrupt và throwInterruptedException.- Không tốn OS thread khi sleep → interrupt đến ngay không cần wake OS thread.
Kết quả: cancel propagate nhanh, resource giải phóng sớm.
9. Pitfall — lỗi thường gặp
Pitfall 1 — fork sau join
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<String> a = scope.fork(() -> taskA());
scope.join(); // join truoc khi fork B
Subtask<String> b = scope.fork(() -> taskB()); // IllegalStateException!
}
fork() chỉ hợp lệ trước join(). Sau khi join() được gọi, scope không nhận fork mới.
Pitfall 2 — quên try-with-resources
// BAD -- scope khong bao gio close
StructuredTaskScope.ShutdownOnFailure scope = new StructuredTaskScope.ShutdownOnFailure();
scope.fork(() -> taskA());
scope.join();
// neu exception thrown, scope.close() khong duoc goi
// subtask A co the con running -- resource leak
Luôn dùng try (var scope = ...). AutoCloseable đảm bảo close() được gọi ngay cả khi exception.
Pitfall 3 — subtask.get() trước join
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<String> a = scope.fork(() -> taskA());
String result = a.get(); // IllegalStateException -- subtask may not be done
scope.join();
}
subtask.get() chỉ được gọi sau scope.join() hoặc sau khi subtask.state() là SUCCESS.
Pitfall 4 — scope tái sử dụng
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// Round 1
scope.fork(() -> taskA());
scope.join();
// Round 2 -- KHONG duoc dung lai cung scope
scope.fork(() -> taskB()); // may throw IllegalStateException
}
Mỗi scope dùng 1 lần. Nếu cần nhiều round, tạo scope mới.
10. So sánh với CompletableFuture
| Tiêu chí | CompletableFuture.allOf() | StructuredTaskScope.ShutdownOnFailure |
|---|---|---|
| Cancellation khi 1 fail | Không tự động | Tự động — interrupt subtasks |
| Lifecycle | Không có — task sống độc lập | Scope bound — task không sống quá scope |
| Error propagation | Phải exceptionally/handle | throwIfFailed() rethrow gọn |
| Collect kết quả | Phức tạp — .thenRun + .join() | subtask.get() sau scope.join() |
| Code style | Callback chain (async) | Sequential (blocking style) |
| Virtual thread tích hợp | Manual (truyền VT executor) | Mặc định (fork tạo VT) |
| Custom policy | Không có built-in | Extend StructuredTaskScope |
| Fluent chain/compose | Rất phong phú | Không — chỉ fork/join |
| Java version | Java 8+ | Java 25 GA (preview từ Java 21) |
Khi nào dùng cái nào:
- StructuredTaskScope: orchestrate finite set task biết trước, cần cancel propagation, code dễ đọc, Java 25+.
- CompletableFuture: fluent async pipeline (chain, transform, compose), reactive-like data flow, cần tích hợp với library trả CF, Java 8+.
- Cả hai có thể cùng tồn tại: dùng Structured Concurrency cho orchestration scope, CF cho async I/O adapter.
11. Mermaid — lifecycle của StructuredTaskScope
sequenceDiagram
participant Owner as Scope Owner Thread
participant S as StructuredTaskScope
participant A as Subtask A (VT)
participant B as Subtask B (VT)
participant C as Subtask C (VT)
Owner->>S: new ShutdownOnFailure()
Owner->>S: fork(taskA) -- start A
S-->>A: spawn virtual thread
Owner->>S: fork(taskB)
S-->>B: spawn virtual thread
Owner->>S: fork(taskC)
S-->>C: spawn virtual thread
Owner->>S: join() -- wait
Note over A: taskA running (200ms)
Note over B: taskB running (300ms)
C->>S: FAILED after 100ms
S->>A: interrupt (ShutdownOnFailure policy)
S->>B: interrupt
A-->>S: cancelled
B-->>S: cancelled
S-->>Owner: join() returns
Owner->>S: throwIfFailed() -- rethrows C's exception
Owner->>S: close() (try-with-resources)12. Deep Dive
- JEP 505 — Structured Concurrency (Java 25 GA) — openjdk.org/jeps/505 — spec cuối cùng. Đọc phần "Motivation" để hiểu rationale, phần "Description" cho API spec chính thức.
- JEP 499 (Java 24 preview) — openjdk.org/jeps/499 — changes từ preview cuối sang GA.
- JEP 480 (Java 23 preview) — openjdk.org/jeps/480 — API stabilization.
- JEP 462 (Java 21 second preview) — openjdk.org/jeps/462 — điểm baseline để đọc nếu cần hiểu lịch sử.
- Ron Pressler — "Notes on Structured Concurrency" — cr.openjdk.org/~rpressler/loom/ — chief designer Project Loom giải thích design rationale. Đặc biệt phần so sánh với Erlang OTP supervisor tree và Kotlin coroutine structured concurrency.
- Kotlin Coroutines Structured Concurrency — Kotlin implement structured concurrency qua
coroutineScope { }vàCoroutineScope. Đọc để thấy Java lấy cảm hứng (và đơn giản hơn) từ đâu. kotlinlang.org/docs/coroutines-basics.html#structured-concurrency - Erlang OTP Supervisor tree: Erlang có supervisor/worker model từ những năm 1990 — process bị lỗi được restart bởi supervisor, scope/lifecycle tự động. Structured Concurrency Java lấy cảm hứng từ "phần lifecycle" của pattern này.
java.util.concurrent.StructuredTaskScopeJavadoc Java 25 — đọchandleComplete()spec,Subtask.Stateenum, vàShutdownOnFailure/ShutdownOnSuccessJavadoc để hiểu contract chính xác.
13. Self-check
Q1Vì sao StructuredTaskScope đảm bảo subtask không sống lâu hơn scope? Cơ chế nào ở tầng JVM thực thi điều này?▸
StructuredTaskScope đảm bảo subtask không sống lâu hơn scope? Cơ chế nào ở tầng JVM thực thi điều này?StructuredTaskScope implement AutoCloseable. Khi thoát try-with-resources — dù normal hay exception — JVM gọi scope.close().
close() làm 2 việc:
- Gọi
Thread.interrupt()trên tất cả virtual thread đang chạy trong scope (đã fork nhưng chưa xong). - Block cho đến khi tất cả virtual thread kết thúc (join internally).
Virtual Thread respond interrupt nhanh: IO operation (socket.read(), Thread.sleep(), Lock.lockInterruptibly()) throw InterruptedException khi thread bị interrupt. Thread exit. close() unblocks.
Đây là lý do Virtual Thread là "native medium" của Structured Concurrency: interrupt-responsive, không tốn OS thread khi block → cancel cheap và nhanh. Platform thread cũng hoạt động nhưng ít hiệu quả hơn.
Hậu quả: sau scope.close(), không có subtask nào còn running. Guarantee mạnh hơn bất kỳ CompletableFuture manual cancel nào.
Q2Khác biệt giữa ShutdownOnFailure và ShutdownOnSuccess? Cho ví dụ use case phù hợp với mỗi policy.▸
ShutdownOnFailure và ShutdownOnSuccess? Cho ví dụ use case phù hợp với mỗi policy.ShutdownOnFailure: shutdown (cancel tất cả) khi bất kỳ subtask nào fail. Dùng khi tất cả task phải thành công — fail 1 là fail toàn bộ operation.
Use case: fetch user + fetch orders + fetch permissions. Tất cả cần thiết để render page. 1 fail → không thể render → fail nhanh, không lãng phí resource chờ 2 task còn lại.
ShutdownOnSuccess: shutdown khi bất kỳ subtask nào thành công. Kết quả đầu tiên thắng, cancel phần còn lại.
Use case: hedged request — gọi primary DB, replica DB, và cache cùng lúc. Lấy kết quả từ source nhanh nhất. Không cần chờ 3 kết quả — 1 là đủ. Tiết kiệm latency.
Điểm tinh tế: ShutdownOnSuccess ném ExecutionException từ scope.result() chỉ khi TẤT CẢ subtask fail. Nếu có ít nhất 1 success, exception của các task fail bị bỏ qua. Đây là behavior phù hợp: hedged request "quan tâm" đến kết quả đầu tiên, không quan tâm failure của task chậm hơn.
Q3So sánh CompletableFuture.allOf() với StructuredTaskScope.ShutdownOnFailure: khi nào nên chọn cái nào?▸
CompletableFuture.allOf() với StructuredTaskScope.ShutdownOnFailure: khi nào nên chọn cái nào?Chọn StructuredTaskScope khi:
- Cần cancel propagation tự động: 1 fail → cancel tất cả ngay, không leak resource.
- Muốn code sequential style dễ đọc: fork → join → get results, không callback chain.
- Tất cả subtask biết trước (finite set), không dynamic.
- Java 25+ available.
- Muốn custom policy (majority vote, first N succeed, etc).
Chọn CompletableFuture khi:
- Cần fluent chain/transform:
thenApply,thenCompose,exceptionally. - Cần dynamic task graph: kết quả task A quyết định có fork task B không.
- API caller expect
CompletableFuture<T>(interop với thư viện). - Java 8 - 24 (không có Java 25 GA).
- Cần
anyOfvới typed result phức tạp hơnShutdownOnSuccess.
Trong cùng 1 service: dùng StructuredTaskScope cho orchestration scope (tầng service), dùng CompletableFuture cho async I/O adapter (tầng infrastructure). Cả hai sống tốt cùng nhau.
Q4Vì sao scope.fork() tạo Virtual Thread thay vì platform thread? Lợi ích cụ thể là gì với 10000 subtask I/O-bound?▸
scope.fork() tạo Virtual Thread thay vì platform thread? Lợi ích cụ thể là gì với 10000 subtask I/O-bound?scope.fork() dùng Thread.ofVirtual().start(callable) bên trong. Virtual Thread (bài 05) là user-space thread do JVM quản — tạo tốn ~1μs, stack ~300 byte, không tốn OS thread.
Với 10000 subtask I/O-bound (ví dụ 10000 HTTP call):
- Platform thread pool 200: 10000/200 = 50 batch × 200ms = 10 giây. Thread không đủ.
- Virtual Thread 10000: tất cả chạy song song. Khi block I/O, JVM unmount khỏi carrier (platform thread). Carrier free, xử lý VT khác. Số carrier = số CPU core (8-16). Tổng latency ~200ms (latency 1 call).
10000 virtual thread ~= 3GB memory stack (10000 × 300B) thay vì 10GB (10000 × 1MB platform thread). Feasible trên server thường.
Về cancel: khi scope shutdown, Thread.interrupt() trên virtual thread respond nhanh vì IO operations (socket, sleep) là interrupt-aware. Platform thread khi block OS IO cũng cần interrupt nhưng OS thread có overhead wake-up lớn hơn.
Kết hợp Structured Concurrency + Virtual Thread là pattern chính thức của Java 25 cho I/O orchestration — thay thế reactive programming mà không sacrifice scale.
Q5Đoạn code sau có bug không? Nếu có, bug gì và sửa thế nào?
var scope = new StructuredTaskScope.ShutdownOnFailure(); Subtask<String> t = scope.fork(() -> fetchData()); scope.join(); return t.get();▸
var scope = new StructuredTaskScope.ShutdownOnFailure(); Subtask<String> t = scope.fork(() -> fetchData()); scope.join(); return t.get();Có bug: không dùng try-with-resources cho scope. Nếu fetchData() throw exception, scope.throwIfFailed() rethrow, hoặc t.get() throw — scope không bao giờ được close().
Hậu quả: subtask đang chạy (nếu có) không bị cancel. Virtual Thread leak. Trong service xử lý nhiều request, mỗi request leak 1 scope → memory và thread leak tích lũy.
Sửa:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<String> t = scope.fork(() -> fetchData());
scope.join();
scope.throwIfFailed();
return t.get();
}Thêm: thiếu scope.throwIfFailed() trước t.get(). Nếu fetchData() fail, t.get() throw IllegalStateException("Subtask not completed successfully") thay vì exception gốc — khó debug. throwIfFailed() rethrow exception thực của subtask.
Rule nhớ: bộ 3 bắt buộc sau scope.fork(): join() → throwIfFailed() → get(). Thiếu bất kỳ bước nào là bug.
Q6Vì sao Structured Concurrency được mô tả là đưa nguyên tắc "structured programming" vào concurrency? Giải thích analogy với function call stack.▸
Trong structured programming (Dijkstra, 1960s): không dùng goto, code có cấu trúc rõ ràng với block, loop, function. Mỗi function return trước khi caller tiếp tục. Call stack tự động đảm bảo: nếu A gọi B gọi C, C phải return trước B, B phải return trước A. Lifetime rõ ràng, không "dangling call".
Với concurrent task truyền thống (submit to thread pool): "fire and forget" — task chạy độc lập, không có cấu trúc. Task B submit trong context A nhưng có thể sống lâu hơn A. Không có stack equivalent. Lifetime mù mờ, cancel phức tạp, error propagation thủ công.
Structured Concurrency mang lại "stack" cho concurrent task: scope là đơn vị lifetime. Task fork trong scope không thể sống quá scope (giống C không thể return sau B trong call stack). Khi scope close, tất cả subtask kết thúc — guarantee này được JVM thực thi (interrupt + join trong close()).
Hệ quả: code concurrent đọc như code sequential. Có thể reason về lifetime, error, và cancel giống như reason về function call. Debug dễ hơn: stack trace của subtask linked với scope owner. Thread dump hiển thị cây scope/subtask rõ ràng.
Erlang OTP supervisor tree áp tư tưởng tương tự từ những năm 1990. Kotlin coroutine coroutineScope { launch { } } là implementation gần nhất trước Java 25. Java Structured Concurrency mang pattern này vào JDK standard library.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên