Trong Java, một Thread đại diện cho một luồng thực thi độc lập trong chương trình, cho phép thực hiện nhiều công việc đồng thời (multithreading). Java cung cấp nhiều cách khác nhau để tạo một Thread, mỗi cách đều có các ưu điểm riêng biệt. Trong bài viết này, chúng ta sẽ xem xét các cách tạo một Thread và thảo luận về sự khác biệt giữa chúng. Cuối cùng, tôi sẽ chia sẻ quan điểm về cách tạo Thread mà tôi thích và lý do tại sao.
Cách 1: Kế thừa từ lớp Thread
Giới thiệu
Cách đơn giản nhất để tạo một Thread là kế thừa từ lớp Thread và ghi đè phương thức run() để định nghĩa hành vi của thread. Sau đó, bạn có thể tạo một đối tượng của lớp con và gọi phương thức start() để bắt đầu luồng thực thi.
Ví dụ
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
MyThread thread = new MyThread();
thread.start();
Trong ví dụ trên, chúng ta tạo lớp MyThread kế thừa từ Thread và định nghĩa hành vi trong phương thức run(). Phương thức start() được gọi để bắt đầu thực thi thread.
Ưu và nhược điểm
Ưu điểm:
- Cách tiếp cận đơn giản, dễ hiểu và dễ sử dụng.
- Có thể sử dụng trực tiếp các phương thức của lớp
Thread như sleep(), join(), interrupt().
Nhược điểm:
- Không thể kế thừa từ lớp khác vì Java chỉ hỗ trợ kế thừa đơn (single inheritance). Nếu lớp đã kế thừa từ một lớp khác, bạn không thể sử dụng cách này.
- Giới hạn về khả năng tái sử dụng mã, vì hành vi của thread được gắn trực tiếp với lớp
Thread.
Cách 2: Triển khai giao diện Runnable
Giới thiệu
Một cách khác để tạo Thread là triển khai giao diện Runnable. Bạn sẽ cần cung cấp định nghĩa cho phương thức run() và truyền một đối tượng Runnable vào đối tượng Thread để thực thi luồng.
Ví dụ
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
Trong ví dụ này, chúng ta tạo lớp MyRunnable triển khai giao diện Runnable và định nghĩa hành vi trong phương thức run(). Sau đó, chúng ta truyền đối tượng MyRunnable vào lớp Thread và gọi phương thức start().
Ưu và nhược điểm
Ưu điểm:
- Cách tiếp cận linh hoạt hơn vì bạn có thể kế thừa từ các lớp khác trong khi vẫn triển khai
Runnable.
- Khả năng tái sử dụng cao, bạn có thể tách biệt logic của thread và sử dụng lại ở nhiều nơi khác nhau.
Nhược điểm:
- Không trực tiếp kế thừa từ lớp
Thread, nên phải sử dụng đối tượng Thread để khởi chạy thread.
Cách 3: Sử dụng biểu thức Lambda (từ Java 8 trở đi)
Giới thiệu
Từ Java 8, với sự ra đời của biểu thức lambda, bạn có thể tạo Thread một cách gọn gàng hơn khi triển khai giao diện Runnable. Thay vì định nghĩa một lớp riêng biệt, bạn có thể truyền trực tiếp một biểu thức lambda vào Thread.
Ví dụ
Thread thread = new Thread(() -> {
System.out.println("Thread is running");
});
thread.start();
Biểu thức lambda cho phép bạn tạo Thread một cách ngắn gọn và trực quan hơn, đặc biệt là trong những tình huống đơn giản.
Ưu và nhược điểm
Ưu điểm:
- Cách tiếp cận gọn gàng và dễ đọc hơn so với việc triển khai giao diện
Runnable truyền thống.
- Giảm thiểu mã nguồn boilerplate (lặp đi lặp lại).
Nhược điểm:
- Chỉ có thể sử dụng từ Java 8 trở đi.
- Dễ làm mất tính rõ ràng của mã trong các tình huống phức tạp.
Cách 4: Sử dụng Callable và Future
Giới thiệu
Nếu bạn cần kết quả trả về từ thread hoặc cần xử lý ngoại lệ, giao diện Callable là lựa chọn tốt hơn so với Runnable. Callable cho phép trả về kết quả và ném ngoại lệ. Sau đó, bạn có thể sử dụng ExecutorService để quản lý thread và lấy kết quả bằng Future.
Ví dụ
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Thread result";
}
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
String result = future.get(); // Lấy kết quả từ thread
System.out.println(result);
executor.shutdown();
Ở đây, Callable giúp bạn trả về kết quả từ thread thông qua Future. Phương thức call() thay thế run() trong Runnable và có thể ném ngoại lệ.
Ưu và nhược điểm
Ưu điểm:
- Cho phép trả về kết quả từ thread.
- Có thể xử lý ngoại lệ trong quá trình thực thi.
- Thích hợp cho các tác vụ cần tính toán kết quả.
Nhược điểm:
- Phức tạp hơn so với
Runnable trong việc quản lý thread.
- Cần sử dụng thêm các lớp như
ExecutorService và Future.
Cách yêu thích của tôi
Cách tôi thích nhất để tạo Thread trong Java là triển khai giao diện Runnable.
Lý do
- Linh hoạt: Việc sử dụng
Runnable cho phép tôi tách biệt hành vi của thread khỏi lớp cụ thể, giúp mã nguồn dễ tái sử dụng hơn. Ngoài ra, tôi có thể kế thừa từ các lớp khác nếu cần, mà không bị giới hạn bởi việc chỉ có thể kế thừa một lớp trong Java.
- Rõ ràng: Mặc dù lambda là một cách viết gọn gàng, nhưng trong các dự án lớn, tôi ưu tiên sự rõ ràng. Việc tạo một lớp triển khai
Runnable giúp tôi và đồng đội dễ dàng theo dõi và duy trì mã nguồn hơn, đặc biệt khi xử lý các logic phức tạp trong thread.
- Tính tương thích:
Runnable có tính tương thích cao, dễ sử dụng và dễ kết hợp với các framework hoặc API khác trong Java. Điều này giúp tôi linh hoạt hơn khi cần tích hợp với các công cụ quản lý thread khác như ExecutorService.
Tóm lại, Java cung cấp nhiều cách để tạo Thread, bao gồm kế thừa từ Thread, triển khai Runnable, sử dụng lambda, và Callable với Future. Cá nhân tôi ưa thích sử dụng Runnable vì tính linh hoạt, khả năng tái sử dụng, và tính rõ ràng mà nó mang lại trong việc phát triển phần mềm.