# Bài 1: Giới thiệu về CompletableFuture

#### Định nghĩa và Mục đích

`CompletableFuture` là một lớp trong gói `java.util.concurrent` được giới thiệu từ Java 8. Nó là một phần của API `Future`, nhưng mở rộng thêm nhiều tính năng hữu ích cho việc xử lý các tác vụ bất đồng bộ. `CompletableFuture` không chỉ giúp chúng ta xử lý các tác vụ bất đồng bộ mà còn cho phép chúng ta kết hợp nhiều tác vụ, xử lý kết quả khi chúng hoàn thành và quản lý ngoại lệ.

##### Định nghĩa

`CompletableFuture` là một implementation của `Future` cho phép chúng ta xử lý các tác vụ bất đồng bộ một cách dễ dàng và mạnh mẽ. Nó cung cấp một loạt các phương thức để làm việc với các tác vụ không đồng bộ và kết quả của chúng, như `thenApply`, `thenAccept`, `thenRun`, `thenCombine`, `thenCompose`, và nhiều phương thức khác.

##### Mục đích

* **Xử lý bất đồng bộ:** Giúp bạn xử lý các tác vụ bất đồng bộ một cách dễ dàng.
    
* **Kết hợp nhiều tác vụ:** Cho phép bạn kết hợp nhiều tác vụ bất đồng bộ và xử lý kết quả của chúng một cách hiệu quả.
    
* **Quản lý ngoại lệ:** Cung cấp các phương thức để bắt và xử lý ngoại lệ trong các tác vụ bất đồng bộ.
    

#### Lợi ích của CompletableFuture

##### Tối ưu hóa hiệu suất

`CompletableFuture` giúp bạn tối ưu hóa hiệu suất bằng cách cho phép các tác vụ chạy đồng thời mà không cần phải chờ đợi các tác vụ khác hoàn thành. Điều này đặc biệt hữu ích trong các ứng dụng yêu cầu xử lý nhiều tác vụ I/O hoặc tính toán đồng thời.

##### Dễ sử dụng

`CompletableFuture` cung cấp một API dễ sử dụng với nhiều phương thức tiện ích giúp bạn dễ dàng kết hợp và xử lý các tác vụ bất đồng bộ. Bạn không cần phải viết mã phức tạp để quản lý các thread hay xử lý kết quả của các tác vụ.

##### Quản lý ngoại lệ

`CompletableFuture` cung cấp các phương thức để bắt và xử lý ngoại lệ một cách dễ dàng. Bạn có thể sử dụng các phương thức như `exceptionally`, `handle`, và `whenComplete` để xử lý ngoại lệ một cách linh hoạt.

#### Cách Tạo một CompletableFuture

##### Tạo CompletableFuture đơn giản

Bạn có thể tạo một `CompletableFuture` đơn giản bằng cách sử dụng phương thức `supplyAsync` hoặc `runAsync`.

* **supplyAsync:** Tạo và thực thi một tác vụ trả về kết quả.
    
* **runAsync:** Tạo và thực thi một tác vụ không trả về kết quả.
    

###### Ví dụ:

```java
import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        // Tạo một CompletableFuture trả về kết quả
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Hello, World!";
        });

        // Xử lý kết quả khi hoàn thành
        future.thenAccept(result -> {
            System.out.println("Result: " + result);
        });

        // Chờ một chút để thấy kết quả (trong thực tế, bạn có thể sử dụng future.join() hoặc các phương thức khác)
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```

Trong ví dụ này:

* `CompletableFuture.supplyAsync` tạo và thực thi một tác vụ bất đồng bộ trả về kết quả "Hello, World!" sau 1 giây.
    
* `thenAccept` được sử dụng để xử lý kết quả khi tác vụ hoàn thành.
    

##### Tạo CompletableFuture từ giá trị có sẵn

Bạn cũng có thể tạo một `CompletableFuture` từ một giá trị có sẵn bằng cách sử dụng phương thức `completedFuture`.

###### Ví dụ:

```java
import java.util.concurrent.CompletableFuture;

public class CompletableFutureCompletedExample {
    public static void main(String[] args) {
        // Tạo một CompletableFuture từ giá trị có sẵn
        CompletableFuture<String> future = CompletableFuture.completedFuture("Hello, World!");

        // Xử lý kết quả ngay lập tức
        future.thenAccept(result -> {
            System.out.println("Result: " + result);
        });
    }
}
```

Trong ví dụ này:

* `CompletableFuture.completedFuture` tạo một `CompletableFuture` đã hoàn thành với giá trị "Hello, World!".
    
* `thenAccept` được sử dụng để xử lý kết quả ngay lập tức.
    

#### Các Phương Thức Chính của CompletableFuture

##### thenApply

Phương thức `thenApply` được sử dụng để xử lý kết quả của một `CompletableFuture` và trả về một kết quả mới.

###### Ví dụ:

```java
import java.util.concurrent.CompletableFuture;

public class CompletableFutureThenApplyExample {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5)
            .thenApply(result -> result * 2);

        future.thenAccept(result -> {
            System.out.println("Result: " + result); // Result: 10
        });

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```

Trong ví dụ này:

* `CompletableFuture.supplyAsync` tạo và thực thi một tác vụ trả về kết quả là 5.
    
* `thenApply` nhân đôi kết quả đó (5 \* 2 = 10).
    
* `thenAccept` in kết quả cuối cùng.
    

##### thenAccept

Phương thức `thenAccept` được sử dụng để xử lý kết quả của một `CompletableFuture` mà không trả về kết quả mới.

###### Ví dụ:

```java
import java.util.concurrent.CompletableFuture;

public class CompletableFutureThenAcceptExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "Hello")
            .thenAccept(result -> {
                System.out.println("Result: " + result); // Result: Hello
            });

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```

Trong ví dụ này:

* `CompletableFuture.supplyAsync` tạo và thực thi một tác vụ trả về kết quả "Hello".
    
* `thenAccept` in kết quả "Hello".
    

##### thenRun

Phương thức `thenRun` được sử dụng để thực thi một tác vụ khác sau khi `CompletableFuture` hoàn thành mà không cần kết quả từ tác vụ trước.

###### Ví dụ:

```java
import java.util.concurrent.CompletableFuture;

public class CompletableFutureThenRunExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "Hello")
            .thenRun(() -> {
                System.out.println("Task completed"); // Task completed
            });

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```

Trong ví dụ này:

* `CompletableFuture.supplyAsync` tạo và thực thi một tác vụ trả về kết quả "Hello".
    
* `thenRun` thực thi một tác vụ khác (in "Task completed") sau khi `CompletableFuture` hoàn thành.
    

#### Kết Luận

Trong bài viết này, chúng ta đã giới thiệu về `CompletableFuture`, một công cụ mạnh mẽ trong Java để xử lý các tác vụ bất đồng bộ. Chúng ta đã tìm hiểu về các lợi ích của việc sử dụng `CompletableFuture`, cách tạo nó và các phương thức chính để làm việc với nó. Qua các ví dụ cụ thể, chúng ta đã thấy cách `CompletableFuture` giúp tối ưu hóa hiệu suất và quản lý các tác vụ một cách hiệu quả.

### Bài Tập Thực Hành

1. Tạo một `CompletableFuture` sử dụng `supplyAsync` để thực thi một tác vụ trả về một chuỗi sau 2 giây. Sử dụng `thenAccept` để in kết quả khi tác vụ hoàn thành.
    
2. Tạo một `CompletableFuture` từ giá trị có sẵn bằng cách sử dụng `completedFuture`. Sử dụng `thenRun` để thực thi một tác vụ khác sau khi `CompletableFuture` hoàn thành.
    

### Lời Khuyên và Best Practices

* **Tận dụng các phương thức của** `CompletableFuture`: Sử dụng các phương thức như `thenApply`, `thenAccept`, và `thenRun` để xử lý kết quả và thực thi các tác vụ liên tiếp một cách hiệu quả.
    
* **Xử lý ngoại lệ:** Đảm bảo rằng bạn bắt và xử lý ngoại lệ trong các tác vụ bất đồng bộ để tránh các lỗi không mong muốn.
    
* **Tối ưu hóa hiệu suất:** Sử dụng `CompletableFuture` để tối ưu hóa hiệu suất ứng dụng bằng cách xử lý đồng thời nhiều tác vụ.
