Skip to main content

Executors và Thread Pools trong Java

Trong Java, Executors cung cấp một cách tiếp cận mạnh mẽ để quản lý các luồng (threads) thông qua các thread poolsscheduling (lập lịch thực thi). Thay vì phải tự tạo và quản lý các đối tượng Thread, bạn có thể sử dụng các Executor để tái sử dụng các luồng, giúp giảm thiểu chi phí khởi tạo và tăng hiệu năng cũng như độ ổn định của ứng dụng. Dưới đây là bài viết chi tiết giới thiệu về các implementation của Executors, thread pools và scheduling, kèm theo ví dụ sử dụng thực tế.


1. Tổng Quan Về Executors

1.1. Executor Framework

Executor Framework được giới thiệu từ Java 5 trong package java.util.concurrent. Nó cung cấp một cách trừu tượng để thực thi các tác vụ (tasks) bất đồng bộ mà không cần quản lý trực tiếp các luồng. Một số giao diện và lớp chính bao gồm:

  • Executor: Giao diện cơ bản với phương thức execute(Runnable command).
  • ExecutorService: Mở rộng từ Executor, cung cấp các phương thức quản lý vòng đời của tác vụ như submit(), shutdown(), awaitTermination().
  • ScheduledExecutorService: Hỗ trợ thực thi tác vụ theo lịch (delay, lặp lại định kỳ).

1.2. Lợi ích Khi Sử Dụng Executors

  • Tái sử dụng tài nguyên: Thay vì tạo và hủy nhiều Thread, bạn sử dụng thread pool để tái sử dụng luồng, giảm tải cho hệ thống.
  • Quản lý vòng đời dễ dàng: Cung cấp các phương thức để dừng, chờ và theo dõi trạng thái của các tác vụ.
  • Lập lịch thực thi: Hỗ trợ chạy tác vụ theo lịch cố định hoặc với độ trễ xác định.

2. Các Loại Thread Pools

Java cung cấp nhiều loại thread pool thông qua lớp tiện ích Executors:

2.1. Fixed Thread Pool

  • Đặc điểm: Tạo một pool có số lượng luồng cố định. Các tác vụ được gửi vào sẽ được thực thi bởi các luồng có sẵn.
  • Ưu điểm: Dễ kiểm soát, tránh quá tải hệ thống.
  • Ví dụ: Sử dụng Executors.newFixedThreadPool(int nThreads).
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExample {
public static void main(String[] args) {
// Tạo thread pool với 3 luồng
ExecutorService executor = Executors.newFixedThreadPool(3);

// Gửi 10 tác vụ vào pool
for (int i = 1; i <= 10; i++) {
int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " được thực thi bởi " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // Mô phỏng công việc nặng
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}

// Tắt executor sau khi hoàn tất công việc
executor.shutdown();
}
}

2.2. Cached Thread Pool

  • Đặc điểm: Tạo ra một thread pool với số lượng luồng có thể tăng giảm linh hoạt theo số lượng tác vụ. Các luồng không sử dụng trong một khoảng thời gian nhất định sẽ bị hủy.
  • Ưu điểm: Phù hợp với các tác vụ ngắn hạn, số lượng tác vụ không ổn định.
  • Ví dụ: Sử dụng Executors.newCachedThreadPool().
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolExample {
public static void main(String[] args) {
// Tạo cached thread pool
ExecutorService executor = Executors.newCachedThreadPool();

// Gửi 10 tác vụ vào pool
for (int i = 1; i <= 10; i++) {
int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " được thực thi bởi " + Thread.currentThread().getName());
try {
Thread.sleep(500); // Mô phỏng tác vụ ngắn hạn
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}

// Tắt executor sau khi hoàn tất
executor.shutdown();
}
}

2.3. Single Thread Executor

  • Đặc điểm: Tạo một pool chỉ có duy nhất một luồng. Tất cả các tác vụ sẽ được thực hiện theo thứ tự FIFO.
  • Ưu điểm: Đảm bảo thứ tự thực thi, phù hợp với các tác vụ cần chạy tuần tự.
  • Ví dụ: Sử dụng Executors.newSingleThreadExecutor().
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorExample {
public static void main(String[] args) {
// Tạo single thread executor
ExecutorService executor = Executors.newSingleThreadExecutor();

// Gửi 5 tác vụ vào executor
for (int i = 1; i <= 5; i++) {
int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " được thực thi bởi " + Thread.currentThread().getName());
try {
Thread.sleep(700);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}

// Tắt executor sau khi hoàn tất
executor.shutdown();
}
}

3. Scheduling với ScheduledExecutorService

3.1. Lý thuyết về ScheduledExecutorService

ScheduledExecutorService mở rộng từ ExecutorService và hỗ trợ lập lịch thực thi các tác vụ theo thời gian nhất định. Bạn có thể:

  • Delay Execution: Thực hiện một tác vụ sau một khoảng thời gian trễ xác định.
  • Periodic Execution: Thực hiện lặp lại tác vụ theo khoảng thời gian cố định (fixed rate hoặc fixed delay).

3.2. Các Phương Thức Chính

  • schedule(Runnable command, long delay, TimeUnit unit): Thực thi một tác vụ sau một khoảng thời gian delay.
  • scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit): Thực thi lặp lại tác vụ với khoảng thời gian cố định giữa các lần bắt đầu.
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit): Thực thi lặp lại tác vụ với khoảng thời gian cố định giữa thời điểm kết thúc của tác vụ trước và bắt đầu của tác vụ kế tiếp.

3.3. Ví dụ Sử Dụng ScheduledExecutorService

a. Thực thi tác vụ sau một khoảng delay

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledTaskExample {
public static void main(String[] args) {
// Tạo ScheduledExecutorService với 2 luồng
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

// Lên lịch thực hiện tác vụ sau 3 giây
scheduler.schedule(() -> {
System.out.println("Tác vụ được thực hiện sau 3 giây bởi " + Thread.currentThread().getName());
}, 3, TimeUnit.SECONDS);

// Tắt scheduler sau khi hoàn tất (trong thực tế, bạn nên sử dụng shutdown sau khi đảm bảo không còn tác vụ nào cần thực thi)
scheduler.shutdown();
}
}

b. Thực thi tác vụ lặp lại theo fixed rate

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class FixedRateTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

// Thực thi tác vụ sau 2 giây, sau đó mỗi 1 giây một lần
scheduler.scheduleAtFixedRate(() -> {
System.out.println("Tác vụ fixed rate chạy bởi " + Thread.currentThread().getName() +
" tại thời điểm: " + System.currentTimeMillis());
}, 2, 1, TimeUnit.SECONDS);

// Chỉ chạy trong 10 giây rồi tắt scheduler
scheduler.schedule(() -> {
scheduler.shutdown();
System.out.println("Scheduler đã tắt.");
}, 10, TimeUnit.SECONDS);
}
}

c. Thực thi tác vụ lặp lại theo fixed delay

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class FixedDelayTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

// Thực thi tác vụ sau 2 giây, sau đó mỗi lần kết thúc tác vụ, chờ 1 giây mới bắt đầu lần tiếp theo
scheduler.scheduleWithFixedDelay(() -> {
System.out.println("Tác vụ fixed delay chạy bởi " + Thread.currentThread().getName() +
" tại thời điểm: " + System.currentTimeMillis());
try {
Thread.sleep(500); // Giả lập thời gian xử lý tác vụ
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, 2, 1, TimeUnit.SECONDS);

// Tắt scheduler sau 10 giây (có thể sử dụng một tác vụ khác để gọi shutdown)
scheduler.schedule(() -> {
scheduler.shutdown();
System.out.println("Scheduler đã tắt.");
}, 10, TimeUnit.SECONDS);
}
}

4. Ứng Dụng Thực Tế

4.1. Xử Lý Yêu Cầu Trong Ứng Dụng Web

Trong các ứng dụng web, khi có hàng trăm hoặc hàng nghìn yêu cầu đồng thời, bạn có thể sử dụng fixed thread pool để giới hạn số lượng luồng thực thi đồng thời, tránh tình trạng quá tải tài nguyên hệ thống.

4.2. Lập Lịch Các Công Việc Định Kỳ

Các tác vụ như gửi email, làm sạch dữ liệu cũ, hoặc đồng bộ hóa dữ liệu giữa các hệ thống thường được lập lịch chạy định kỳ. Sử dụng ScheduledExecutorService giúp bạn dễ dàng cấu hình thời gian thực hiện và quản lý vòng đời của các tác vụ này.

4.3. Xử Lý Tác Vụ Ngắn Hạn

Nếu ứng dụng của bạn có các tác vụ ngắn hạn, không liên tục nhưng số lượng tác vụ không ổn định, cached thread pool sẽ là lựa chọn phù hợp, vì nó tạo mới các luồng khi cần và tái sử dụng khi có thể.


5. Tổng Kết

  • Executor Framework giúp tách biệt công việc gửi yêu cầu thực thi và việc quản lý, khởi tạo các luồng, từ đó cải thiện hiệu năng và khả năng mở rộng của ứng dụng.
  • Các loại Thread Pools như fixed, cached, và single thread executor giúp bạn chọn mô hình phù hợp với từng hoàn cảnh xử lý tác vụ.
  • ScheduledExecutorService mở rộng khả năng lên lịch thực hiện các tác vụ theo delay hoặc theo chu kỳ, rất hữu ích trong các ứng dụng cần chạy công việc định kỳ.
  • Các ví dụ thực tế minh họa cách sử dụng các Executor này giúp bạn dễ dàng tích hợp vào ứng dụng, đảm bảo việc quản lý tài nguyên hiệu quả và tránh các lỗi như tạo quá nhiều Thread.

Nhờ vào việc sử dụng Executors, lập trình viên Java có thể xây dựng các ứng dụng đa luồng mạnh mẽ, ổn định và có khả năng mở rộng cao, đồng thời dễ dàng bảo trì và nâng cấp trong quá trình phát triển.