Mạng xã hội
Hoặc copy link
Trong bài này sharecs.net muốn chia sẻ tới các bạn những câu hỏi hay đào sâu về ThreadPool. Giúp các bạn nắm chắc các kiến thức ThreadPool cho các cuộc phỏng vấn.
ThreadPool trong Java là một cơ chế xử lý đa luồng, cho phép quản lý một tập hợp các luồng thực thi tác vụ. Nó được sử dụng để tối ưu hóa việc sử dụng tài nguyên hệ thống và cải thiện hiệu suất của ứng dụng đa luồng.
ThreadPool cho phép ta quản lý một số lượng cố định các luồng, được sử dụng để thực hiện các tác vụ trong hệ thống. Khi một tác vụ được gửi đến ThreadPool, nó sẽ được chuyển đến một trong những luồng đang chờ sẵn trong ThreadPool để thực hiện. Nếu tất cả các luồng đang bận, tác vụ sẽ được đưa vào hàng đợi cho đến khi một luồng trở thành sẵn sàng.
ThreadPool là một thành phần quan trọng của Java Concurrency API, được cung cấp bởi Java từ phiên bản 1.5 trở đi. Nó được triển khai bởi lớp ThreadPoolExecutor, cung cấp một số phương thức để quản lý các luồng và tác vụ trong ThreadPool.
Các lợi ích của việc sử dụng ThreadPool trong Java bao gồm:
Tóm lại, việc sử dụng ThreadPool trong Java giúp tối ưu hóa tài nguyên, cải thiện hiệu suất, tăng khả năng mở rộng, quản lý tác vụ và tiết kiệm tài nguyên.
Các thành phần chính của ThreadPool trong Java bao gồm:

Trong Java 11, để tạo một ThreadPool, bạn có thể sử dụng lớp Executors để tạo ra một instance của ThreadPoolExecutor với các tham số cấu hình của bạn. Dưới đây là cách tạo một ThreadPool với 10 luồng trong Java 11:
// ví dụ sharecs.net
import java.util.concurrent.*;
public class MyThreadPool {
public static void main(String[] args) {
int numThreads = 10;
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
// sử dụng executor để thực hiện các tác vụ trong ThreadPool
executor.execute(new MyTask());
// sau khi hoàn thành, hãy tắt ThreadPool
executor.shutdown();
}
}
class MyTask implements Runnable {
@Override
public void run() {
// Thực hiện tác vụ sharecs.net
}
}
Ở đây, chúng ta sử dụng Executors.newFixedThreadPool(numThreads) để tạo một instance của ThreadPoolExecutor với 10 luồng để thực hiện các tác vụ trong ThreadPool. Sau đó, chúng ta sử dụng executor.execute(new MyTask()) để thêm một tác vụ mới vào hàng đợi của ThreadPool để thực thi. Khi tất cả các tác vụ đã được thực hiện, chúng ta gọi executor.shutdown() để tắt ThreadPool.
Trong Java, ThreadPoolExecutor là một lớp quan trọng để quản lý và thực thi các tác vụ trong ThreadPool. Dưới đây là các phương thức quan trọng trong ThreadPoolExecutor:
Future để theo dõi kết quả của tác vụ.Các phương thức này cung cấp cho bạn khả năng quản lý và kiểm soát ThreadPool để thực thi các tác vụ một cách hiệu quả.
ThreadPoolExecutor là một lớp linh hoạt cho phép bạn tùy chỉnh các tham số để cấu hình ThreadPool. Dưới đây là các tham số cấu hình được hỗ trợ bởi ThreadPoolExecutor:
Bạn có thể tạo một instance của ThreadPoolExecutor và cấu hình các tham số này bằng cách sử dụng các phương thức khởi tạo phù hợp. Ví dụ, để tạo một ThreadPool với 10 luồng, hàng đợi có thể chứa tối đa 100 tác vụ và các tác vụ bị từ chối sẽ được xử lý bằng cách in ra thông báo, bạn có thể sử dụng mã sau:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // corePoolSize
10, // maximumPoolSize
60, // keepAliveTime
TimeUnit.SECONDS, // unit
new ArrayBlockingQueue<>(100), // workQueue
Executors.defaultThreadFactory(), // threadFactory
(r, exec) -> System.out.println("Task rejected: " + r.toString()) // handler
);
Các tham số này có thể được cấu hình theo nhu cầu của bạn để tạo ra một ThreadPool phù hợp với yêu cầu của ứng dụng của bạn.
Bạn có thể thêm một tác vụ vào ThreadPool bằng cách sử dụng phương thức execute(Runnable command) hoặc submit(Runnable task) của ThreadPoolExecutor.
1. execute(Runnable command) được sử dụng để thêm một tác vụ không đồng bộ vào ThreadPool. Phương thức này không trả về kết quả của tác vụ và không thể hủy bỏ tác vụ sau khi được gửi đến ThreadPool. Ví dụ:
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
executor.execute(() -> {
// Các thao tác cần thực hiện trong tác vụ
});
submit(Runnable task) được sử dụng để thêm một tác vụ không đồng bộ vào ThreadPool và trả về một Future để theo dõi kết quả của tác vụ. Bạn có thể sử dụng Future để kiểm tra trạng thái hoàn thành của tác vụ, hủy bỏ tác vụ hoặc lấy kết quả trả về của tác vụ nếu có. Ví dụ:
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
Future<?> future = executor.submit(() -> {
// Các thao tác cần thực hiện trong tác vụ
});
// sharecs.net Kiểm tra trạng thái hoàn thành của tác vụ
if (future.isDone()) {
// Tác vụ đã hoàn thành
}
//sharecs.net Hủy bỏ tác vụ
future.cancel(true);
// Lấy kết quả trả về của tác vụ nếu có
Object result = future.get();
Lưu ý rằng execute(Runnable command) và submit(Runnable task) đều được sử dụng để thêm tác vụ không đồng bộ vào ThreadPool. Nếu bạn muốn thêm một tác vụ đồng bộ, bạn có thể sử dụng submit(Callable<V> task) thay vì submit(Runnable task) để trả về một Future<V> chứa kết quả trả về của tác vụ.
Để sử dụng ThreadPool trong một ứng dụng đa luồng, bạn có thể thực hiện các bước sau:
Ví dụ dưới đây minh họa cách sử dụng ThreadPool trong một ứng dụng đa luồng:
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws Exception {
// Khởi tạo ThreadPoolExecutor với 10 luồng
ExecutorService executor = Executors.newFixedThreadPool(10);
// Tạo các tác vụ cần thực thi trong ThreadPool
Runnable task1 = new Runnable() {
public void run() {
System.out.println("Task 1 is running");
}
};
Runnable task2 = new Runnable() {
public void run() {
System.out.println("Task 2 is running");
}
};
// Thêm các tác vụ vào ThreadPoolExecutor
executor.execute(task1);
executor.execute(task2);
// Đảm bảo các tác vụ đã hoàn thành
executor.shutdown();
// Theo dõi kết quả của các tác vụ
Future<?> future1 = executor.submit(task1);
Future<?> future2 = executor.submit(task2);
System.out.println("Result 1: " + future1.get());
System.out.println("Result 2: " + future2.get());
}
}
Trong ví dụ trên, chúng ta sử dụng Executors.newFixedThreadPool(10) để khởi tạo một ThreadPoolExecutor với 10 luồng. Chúng ta tạo hai tác vụ task1 và task2, sau đó thêm chúng vào ThreadPoolExecutor bằng cách sử dụng phương thức execute().
Sau đó, chúng ta đảm bảo rằng các tác vụ đã được thêm vào ThreadPoolExecutor đã hoàn thành bằng cách sử dụng phương thức shutdown(). Cuối cùng, chúng ta sử dụng submit() để thêm lại các tác vụ và sử dụng.
Có thể đồng bộ hóa tác vụ trong ThreadPool bằng cách sử dụng Future và phương thức get(). Khi bạn thêm một tác vụ vào ThreadPool, phương thức submit() của ExecutorService sẽ trả về một đối tượng Future mà bạn có thể sử dụng để đồng bộ hóa tác vụ đó.
Phương thức get() của đối tượng Future sẽ chặn luồng hiện tại cho đến khi tác vụ được hoàn thành, sau đó trả về kết quả của tác vụ đó. Nếu tác vụ gặp lỗi, phương thức get() sẽ ném ra một ngoại lệ.
Ví dụ sau đây minh họa cách đồng bộ hóa một tác vụ trong ThreadPool bằng cách sử dụng Future:
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws Exception {
//sharecs.net Khởi tạo ThreadPoolExecutor với 10 luồng
ExecutorService executor = Executors.newFixedThreadPool(10);
// Tạo một tác vụ và thêm vào ThreadPoolExecutor
Callable<Integer> task = new Callable<Integer>() {
public Integer call() {
return 2 + 2;
}
};
Future<Integer> future = executor.submit(task);
// Đợi cho tác vụ hoàn thành và in ra kết quả
int result = future.get();
System.out.println("Result sharecs.net: " + result);
// Đảm bảo các tác vụ đã hoàn thành
executor.shutdown();
}
}
Trong ví dụ trên, chúng ta tạo một tác vụ task có thể trả về một giá trị Integer, sau đó thêm nó vào ThreadPoolExecutor bằng cách sử dụng phương thức submit().
Sau đó, chúng ta lấy đối tượng Future trả về bởi phương thức submit() và sử dụng phương thức get() để đợi cho tác vụ hoàn thành. Kết quả của tác vụ được lưu trữ trong biến result, và chúng ta in ra kết quả đó.
Cuối cùng, chúng ta đảm bảo rằng các tác vụ đã hoàn thành bằng cách sử dụng phương thức shutdown().
Để tắt một ThreadPool trong Java, bạn có thể sử dụng phương thức shutdown() hoặc shutdownNow() của ExecutorService.
Phương thức shutdown() sẽ yêu cầu ExecutorService dừng nhận các tác vụ mới và chờ cho tất cả các tác vụ hiện tại được hoàn thành trước khi tắt ThreadPool. Sau khi tất cả các tác vụ đã hoàn thành, ThreadPool sẽ dừng hoạt động và không thể được sử dụng lại.
Phương thức shutdownNow() cũng yêu cầu ExecutorService dừng nhận các tác vụ mới, nhưng nó cũng sẽ cố gắng ngay lập tức dừng tất cả các tác vụ đang chạy và trả về danh sách các tác vụ chưa được thực thi. Phương thức này có thể trả về một danh sách trống nếu tất cả các tác vụ đã hoàn thành hoặc nếu không có tác vụ nào được thực thi.
Ví dụ sau đây minh họa cách tắt ThreadPool bằng cách sử dụng phương thức shutdown():
ExecutorService executor = Executors.newFixedThreadPool(10);
// ... thêm các tác vụ vào ThreadPool
// Tắt ThreadPool
executor.shutdown();
// Đợi cho tất cả các tác vụ hoàn thành
while (!executor.isTerminated()) {
try {
executor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
// Xử lý ngoại lệ
}
}
Trong ví dụ trên, chúng ta tạo một ThreadPool với 10 luồng và thêm các tác vụ vào ThreadPool. Sau đó, chúng ta gọi phương thức shutdown() để tắt ThreadPool.
Để đợi cho tất cả các tác vụ hoàn thành, chúng ta sử dụng vòng lặp while kết hợp với phương thức isTerminated() và awaitTermination() của ExecutorService. Vòng lặp này sẽ chạy cho đến khi tất cả các tác vụ đã hoàn thành, và phương thức awaitTermination() sẽ chặn vòng lặp trong một khoảng thời gian nhất định (1 giây trong ví dụ trên) trước khi tiếp tục chạy vòng lặp. Nếu phương thức awaitTermination() ném ra ngoại lệ InterruptedException, chúng ta cần phải xử lý ngoại lệ đó.
ThreadLocal trong Java là một lớp được sử dụng để lưu trữ dữ liệu địa phương (local data) cho mỗi luồng (thread) khác nhau. Với ThreadLocal, mỗi luồng có thể có một bản sao riêng của dữ liệu được lưu trữ, và dữ liệu này không thể truy cập được từ các luồng khác.
Các trường hợp sử dụng phổ biến của ThreadLocal bao gồm:
Ví dụ sau đây minh họa cách sử dụng ThreadLocal để lưu trữ thông tin người dùng đăng nhập:
public class UserContext {
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
public static void setCurrentUser(User user) {
currentUser.set(user);
}
public static User getCurrentUser() {
return currentUser.get();
}
Trong ví dụ trên, chúng ta khai báo một ThreadLocal là currentUser, được sử dụng để lưu trữ thông tin người dùng đăng nhập cho mỗi luồng. Phương thức setCurrentUser được sử dụng để thiết lập thông tin người dùng cho luồng hiện tại, trong khi getCurrentUser được sử dụng để truy xuất thông tin người dùng đối với luồng hiện tại. Khi một luồng khác truy cập phương thức getCurrentUser, nó sẽ nhận được bản sao của đối tượng User được lưu trữ cho luồng đó.
Mặc dù ThreadPool có nhiều lợi ích, nhưng cũng có một số nhược điểm sau đây:
Cảm ơn các bạn đã ghé thăm Sharecs.net Chúc các bạn thành công!