Bài 3: Các loại ExecutorService
Trong bài viết này, chúng ta sẽ tìm hiểu về các loại ExecutorService phổ biến và cách sử dụng chúng. Mỗi loại ExecutorService có các đặc điểm và ứng dụng riêng, giúp bạn lựa chọn phù hợp với yêu cầu cụ thể của ứng dụng.
Fixed Thread Pool
FixedThreadPool tạo ra một pool với số lượng thread cố định. Điều này hữu ích khi bạn biết trước số lượng task sẽ không vượt quá một ngưỡng nhất định.
Cách tạo và sử dụng FixedThreadPool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
Runnable worker = new WorkerThread("Task " + i);
executorService.submit(worker);
}
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
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:
FixedThreadPoolđược tạo với 3 thread.5 task được gửi đến
ExecutorService, trong đó chỉ có 3 task được chạy đồng thời, 2 task còn lại sẽ chờ cho đến khi có thread rảnh.
Cached Thread Pool
CachedThreadPool tạo ra một pool với số lượng thread linh hoạt. Nó tạo thread mới khi cần và tái sử dụng các thread nhàn rỗi. Điều này hữu ích cho các ứng dụng có số lượng task thay đổi không đều.
Cách tạo và sử dụng CachedThreadPool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 1; i <= 5; i++) {
Runnable worker = new WorkerThread("Task " + i);
executorService.submit(worker);
}
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All tasks finished.");
}
}
Trong ví dụ này:
CachedThreadPoolđược tạo ra, cho phép tạo thread mới khi cần thiết và tái sử dụng các thread nhàn rỗi.5 task được gửi đến
ExecutorServicevà tất cả đều có thể chạy đồng thời nếu cần.
Single Thread Executor
SingleThreadExecutor tạo một executor chỉ có một thread. Điều này hữu ích cho các task cần đảm bảo thứ tự thực thi.
Cách tạo và sử dụng SingleThreadExecutor
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 5; i++) {
Runnable worker = new WorkerThread("Task " + i);
executorService.submit(worker);
}
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All tasks finished.");
}
}
Trong ví dụ này:
SingleThreadExecutorđảm bảo rằng các task được thực thi tuần tự theo thứ tự chúng được gửi đến.
Scheduled Executor Service
ScheduledExecutorService mở rộng ExecutorService và cung cấp các khả năng thực thi các task định kỳ hoặc có độ trễ.
Cách tạo và sử dụng ScheduledExecutorService
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceExample {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
// Schedule a task to run after a delay of 3 seconds
scheduledExecutorService.schedule(new WorkerThread("Task with delay"), 3, TimeUnit.SECONDS);
// Schedule a task to run repeatedly every 2 seconds
scheduledExecutorService.scheduleAtFixedRate(new WorkerThread("Repeating Task"), 1, 2, TimeUnit.SECONDS);
// Schedule a task to run repeatedly with a fixed delay
scheduledExecutorService.scheduleWithFixedDelay(new WorkerThread("Fixed Delay Task"), 1, 2, TimeUnit.SECONDS);
// Shutdown the executor after some time to allow tasks to run
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduledExecutorService.shutdown();
try {
scheduledExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All tasks finished.");
}
}
Trong ví dụ này:
ScheduledExecutorServiceđược tạo với 3 thread.Một task được lập lịch để chạy sau 3 giây.
Một task được lập lịch để chạy định kỳ mỗi 2 giây.
Một task khác được lập lịch để chạy định kỳ với độ trễ cố định sau mỗi lần chạy.
Kết luận
Trong bài viết này, chúng ta đã tìm hiểu về các loại ExecutorService phổ biến: FixedThreadPool, CachedThreadPool, SingleThreadExecutor, và ScheduledExecutorService. Mỗi loại có các đặc điểm và ứng dụng riêng, giúp bạn lựa chọn phù hợp với yêu cầu cụ thể của ứng dụng.
Bài tập thực hành
Tạo một
FixedThreadPoolvới 4 thread 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.Sử dụng
CachedThreadPoolđể gửi 15 task đồng thời và in ra số thứ tự của từng task và tên thread thực thi.Tạo một
ScheduledExecutorServiceđể lập lịch một task chạy sau 5 giây và một task chạy định kỳ mỗi 3 giây.
Lời khuyên và Best Practices
Chọn loại
ExecutorServicephù hợp: Xem xét yêu cầu cụ thể của ứng dụng để chọn loạiExecutorServicetối ưu.Tối ưu hóa kích thước pool: Với
FixedThreadPool, cân nhắc số lượng thread phù hợp để tránh tình trạng thừa hoặc thiếu tài nguyên.Quản lý vòng đời của Executor: Luôn gọi
shutdown()vàawaitTermination()để đảm bảo các task hoàn thành một cách an toàn trước khi ứng dụng kết thúc.