# Bài 2: Cách sử dụng ExecutorService

#### Giao diện ExecutorService

`ExecutorService` là một giao diện trong `java.util.concurrent` cung cấp các phương thức để quản lý vòng đời của các task đồng thời. Nó mở rộng giao diện `Executor` với các phương thức bổ sung cho việc quản lý kết quả của các tác vụ và kiểm soát vòng đời của executor.

##### Các phương thức chính

* **submit(Callable task):** Gửi một task có thể trả về kết quả. Trả về một `Future` đại diện cho kết quả của task.
    
* **submit(Runnable task):** Gửi một task không trả về kết quả. Trả về một `Future` đại diện cho task.
    
* **invokeAll(Collection&lt;? extends Callable&gt; tasks):** Gửi một tập hợp các task và chờ đợi tất cả hoàn thành. Trả về một danh sách các `Future` đại diện cho các kết quả.
    
* **invokeAny(Collection&lt;? extends Callable&gt; tasks):** Gửi một tập hợp các task và trả về kết quả của một task bất kỳ hoàn thành đầu tiên (hoặc ném ngoại lệ nếu tất cả task thất bại).
    

#### Ví dụ cụ thể

##### Sử dụng submit với Runnable và Callable

```java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorServiceSubmitExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // Submit a Runnable task
        Future<?> future1 = executorService.submit(new RunnableTask());

        // Submit a Callable task
        Future<Integer> future2 = executorService.submit(new CallableTask());

        try {
            // Get results from futures
            System.out.println("RunnableTask result: " + future1.get());
            System.out.println("CallableTask result: " + future2.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Shutdown the executor service
        executorService.shutdown();
    }
}

class RunnableTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing RunnableTask");
    }
}

class CallableTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("Executing CallableTask");
        return 42;
    }
}
```

Trong ví dụ này:

* `RunnableTask` là một task không trả về kết quả.
    
* `CallableTask` là một task trả về kết quả.
    
* `submit` được sử dụng để gửi các task đến `ExecutorService`.
    
* Kết quả của `CallableTask` được lấy thông qua `Future`.
    

##### Sử dụng invokeAll và invokeAny

```java
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorServiceInvokeExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        List<Callable<String>> tasks = Arrays.asList(
            new Task("Task 1", 2),
            new Task("Task 2", 1),
            new Task("Task 3", 3)
        );

        try {
            // invokeAll example
            List<Future<String>> futures = executorService.invokeAll(tasks);
            for (Future<String> future : futures) {
                System.out.println("Result: " + future.get());
            }

            // invokeAny example
            String result = executorService.invokeAny(tasks);
            System.out.println("First completed result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Shutdown the executor service
        executorService.shutdown();
    }
}

class Task implements Callable<String> {
    private String name;
    private int duration;

    public Task(String name, int duration) {
        this.name = name;
        this.duration = duration;
    }

    @Override
    public String call() throws Exception {
        Thread.sleep(duration * 1000);
        return name + " completed";
    }
}
```

Trong ví dụ này:

* `invokeAll` gửi một tập hợp các task và đợi tất cả hoàn thành trước khi trả về kết quả.
    
* `invokeAny` gửi một tập hợp các task và trả về kết quả của task hoàn thành đầu tiên.
    

#### Quản lý vòng đời của ExecutorService

##### Shutdown và ShutdownNow

* **shutdown():** Ngăn không cho executor nhận thêm task mới và tiếp tục thực hiện các task hiện có.
    
* **shutdownNow():** Cố gắng dừng ngay lập tức tất cả các task đang chạy và trả về danh sách các task đang chờ.
    

```java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceShutdownExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        for (int i = 1; i <= 5; i++) {
            executorService.submit(new WorkerThread("Task " + i));
        }

        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
        }
        System.out.println("All tasks finished.");
    }
}

class WorkerThread implements Runnable {
    private String message;

    public WorkerThread(String message) {
        this.message = message;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " (Start) message = " + message);
        processMessage();
        System.out.println(Thread.currentThread().getName() + " (End)");
    }

    private void processMessage() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```

Trong ví dụ này:

* `shutdown` được gọi để ngăn không cho executor nhận thêm task mới.
    
* `awaitTermination` chờ cho các task hiện có hoàn thành.
    
* `shutdownNow` được gọi nếu các task không hoàn thành trong thời gian chờ đợi.
    

### Kết luận

Trong bài viết này, chúng ta đã tìm hiểu cách sử dụng `ExecutorService` thông qua các phương thức `submit`, `invokeAll`, `invokeAny`, và cách quản lý vòng đời của executor bằng cách sử dụng `shutdown` và `shutdownNow`. Chúng ta cũng đã xem qua các ví dụ cụ thể để minh họa cách sử dụng các phương thức này trong thực tế.

### Bài tập thực hành

1. Tạo một `ExecutorService` sử dụng `Fixed Thread Pool` và gửi 10 task đồng thời, mỗi task sẽ in ra số thứ tự của nó và tên thread thực thi.
    
2. Sử dụng `invokeAll` để gửi một tập hợp các task và in ra kết quả của tất cả các task sau khi chúng hoàn thành.
    

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

* Luôn luôn gọi `shutdown()` trên `ExecutorService` để đảm bảo rằng các thread được tắt một cách an toàn sau khi hoàn thành công việc.
    
* Sử dụng `Future` để quản lý kết quả và xử lý ngoại lệ từ các task chạy đồng thời.
    
* Chọn loại `ExecutorService` phù hợp với nhu cầu cụ thể của ứng dụng để tối ưu hóa hiệu suất.
