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.