🚀 Làm chủ CompletableFuture trong Java: Ứng dụng và Mã mẫu
Lập trình bất đồng bộ trong Java trước đây thường gặp khó khăn với chỉ Thread, Runnable, hoặc ExecutorService. Kể từ Java 8, CompletableFuture đã trở thành một trong những API mạnh mẽ nhất để xử lý các tác vụ bất đồng bộ với mã nguồn rõ ràng, trôi chảy và không chặn.
Trong bài viết này, chúng ta sẽ khám phá:
- Các khái niệm cơ bản về
CompletableFuture - Các trường hợp sử dụng trong thực tế
- Kết nối và kết hợp futures
- Xử lý ngoại lệ
- Chạy nhiều tác vụ song song
1. Giới thiệu về CompletableFuture
CompletableFuture đại diện cho kết quả trong tương lai của một phép toán bất đồng bộ. Nó có thể được hoàn thành thủ công hoặc thực hiện bất đồng bộ.
Ví dụ: Chạy một tác vụ bất đồng bộ
java
import java.util.concurrent.*;
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
System.out.println("Tác vụ đã được thực hiện bởi: " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
future.get(); // chờ đợi cho đến khi hoàn thành
}
}
📌 Điểm chính: runAsync() chạy một Runnable (không có giá trị trả về), trong khi supplyAsync() chạy một Supplier (có giá trị trả về).
2. Trả về kết quả với supplyAsync
java
import java.util.concurrent.*;
public class CompletableFutureReturn {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) { }
return "Xin chào từ " + Thread.currentThread().getName();
});
System.out.println(future.get()); // chặn cho đến khi có kết quả
}
}
3. Kết nối với thenApply, thenAccept, thenRun
thenApply()→ biến đổi kết quả và trả về giá trị mớithenAccept()→ tiêu thụ kết quả (không trả về)thenRun()→ chạy sau khi hoàn thành (bỏ qua kết quả)
java
import java.util.concurrent.*;
public class CompletableFutureChaining {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> "Dữ liệu")
.thenApply(data -> data + " đã được xử lý")
.thenAccept(System.out::println)
.thenRun(() -> System.out.println("Quá trình hoàn tất!"));
}
}
Kết quả:
Dữ liệu đã được xử lý
Quá trình hoàn tất!
4. Kết hợp nhiều Futures
Ví dụ: thenCombine (gộp hai kết quả)
java
import java.util.concurrent.*;
public class CompletableFutureCombine {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Xin chào");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Thế giới");
CompletableFuture<String> combined = future1.thenCombine(future2, (a, b) -> a + " " + b);
System.out.println(combined.get()); // Xin chào Thế giới
}
}
Ví dụ: allOf (chờ nhiều tác vụ)
java
import java.util.concurrent.*;
public class CompletableFutureAllOf {
public static void main(String[] args) throws Exception {
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Tác vụ 1");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "Tác vụ 2");
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> "Tác vụ 3");
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
all.join();
System.out.println(f1.get() + ", " + f2.get() + ", " + f3.get());
}
}
5. Xử lý ngoại lệ
Sử dụng exceptionally
java
public class CompletableFutureException {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("Có gì đó không ổn!");
return "OK";
}).exceptionally(ex -> "Khôi phục từ: " + ex.getMessage());
System.out.println(future.get()); // Khôi phục từ: Có gì đó không ổn!
}
}
Sử dụng handle
java
public class CompletableFutureHandle {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("Đã xảy ra lỗi!");
return "Thành công";
}).handle((result, ex) -> {
if (ex != null) return "Giá trị dự phòng";
return result;
});
System.out.println(future.get()); // Giá trị dự phòng
}
}
6. Trường hợp sử dụng thực tế: Lấy dữ liệu từ nhiều API
Giả sử bạn muốn lấy thông tin sản phẩm và giá cả từ các dịch vụ khác nhau một cách song song.
java
import java.util.concurrent.*;
public class ProductService {
public static void main(String[] args) throws Exception {
CompletableFuture<String> productFuture = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "Thông tin sản phẩm";
});
CompletableFuture<String> priceFuture = CompletableFuture.supplyAsync(() -> {
sleep(1500);
return "Thông tin giá cả";
});
CompletableFuture<String> finalResult =
productFuture.thenCombine(priceFuture, (product, price) -> product + " + " + price);
System.out.println("Kết quả cuối: " + finalResult.get());
}
private static void sleep(int ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { }
}
}
7. Khi nào nên sử dụng CompletableFuture
✅ Tốt nhất cho:
- Chạy các tác vụ I/O-bound song song (gọi API, truy vấn DB)
- Xây dựng các pipeline biến đổi
- Xử lý các tác vụ bất đồng bộ phụ thuộc
- Tập hợp kết quả từ nhiều phép toán
❌ Tránh cho:
- Các phép toán nặng CPU (tốt hơn nên sử dụng
ForkJoinPoolhoặc các stream song song) - Các tác vụ cực ngắn (chi phí có thể vượt quá lợi ích)
📝 Kết luận
CompletableFuture đơn giản hóa lập trình bất đồng bộ trong Java bằng cách làm cho mã nguồn:
- Dễ đọc (chuỗi trôi chảy)
- Có thể kết hợp (kết hợp tác vụ dễ dàng)
- Đáng tin cậy (xử lý ngoại lệ tích hợp)
Bằng cách sử dụng các mẫu như thenCombine, allOf, và exceptionally, bạn có thể xây dựng các pipeline không chặn hiệu quả cho các ứng dụng thực tế.
👉 Bạn có muốn tôi cũng bao gồm các trường hợp sử dụng nâng cao như thời gian chờ (orTimeout), hủy bỏ, và sử dụng ExecutorService tùy chỉnh trong bài viết này không?