1. Các trạng thái chính của luồng trong Java
Trong Java, một luồng không chỉ đơn giản là “đang chạy” hay “đã dừng”. Nó có một vòng đời đầy đủ, và ở mỗi giai đoạn luồng sẽ cư xử khác nhau. Hiểu các giai đoạn này là chìa khóa để viết ứng dụng đa luồng ổn định và để gỡ lỗi các hiện tượng “treo” và “kết thúc bất ngờ”.
Có những trạng thái nào?
Java định nghĩa các trạng thái chính của luồng (chúng được liệt kê trong enum Thread.State):
| Trạng thái | Mô tả |
|---|---|
|
Luồng đã được tạo nhưng chưa được khởi chạy (new Thread(...), nhưng start() chưa được gọi) |
|
Luồng sẵn sàng thực thi hoặc đang thực thi ngay lúc này |
|
Luồng đang chờ monitor được giải phóng (bị chặn tại khối synchronized) |
|
Luồng đang chờ được luồng khác “đánh thức” (ví dụ qua Object.wait()) |
|
Luồng chờ với thời hạn (ví dụ Thread.sleep(1000), wait(1000), join(1000)) |
|
Luồng đã kết thúc thực thi |
Thông tin thú vị:
Trong các sách và bài viết cũ, bạn có thể bắt gặp các tên gọi khác hoặc sơ đồ hơi khác. Nhưng từ Java 5+ các trạng thái này được coi là tiêu chuẩn.
Sơ đồ trực quan vòng đời của luồng
stateDiagram-v2
[*] --> NEW
NEW --> RUNNABLE: start()
RUNNABLE --> BLOCKED: cố gắng vào synchronized, nhưng monitor đang bận
BLOCKED --> RUNNABLE: monitor đã được giải phóng
RUNNABLE --> WAITING: wait(), join()
RUNNABLE --> TIMED_WAITING: sleep(), wait(timeout), join(timeout)
WAITING --> RUNNABLE: notify()/notifyAll(), join() đã kết thúc
TIMED_WAITING --> RUNNABLE: hết thời hạn / notify()/notifyAll()
RUNNABLE --> TERMINATED: run() đã kết thúc
WAITING --> TERMINATED: run() đã kết thúc (hiếm)
TIMED_WAITING --> TERMINATED: run() đã kết thúc (hiếm)
2. Các phương thức điều khiển luồng
Ngủ: Thread.sleep(long millis)
Đôi khi luồng cần “ngủ” để không cản trở các luồng khác hoặc chờ sự kiện. Phương thức Thread.sleep(ms) đưa luồng vào trạng thái
TIMED_WAITING trong số mili-giây đã chỉ định.
System.out.println("Luồng ngủ trong 2 giây...");
Thread.sleep(2000); // Ngủ 2 giây
System.out.println("Luồng đã thức dậy!");
- Sau khi ngủ xong, luồng quay trở lại trạng thái
(sẵn sàng làm việc).RUNNABLE - Nếu luồng bị ngắt trong khi ngủ, sẽ ném InterruptedException.
Chờ một luồng khác kết thúc: join()
Đôi khi bạn không chỉ khởi chạy luồng, mà còn cần chờ nó hoàn thành. Trong Java có phương thức join():
Thread t = new Thread(() -> {
System.out.println("Đang làm việc...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("Xong!");
});
t.start();
System.out.println("Đang chờ luồng t kết thúc...");
t.join(); // Luồng hiện tại (ví dụ main) chờ t
System.out.println("Luồng t đã kết thúc!");
Ở đây luồng chính bắt đầu chờ ngay khi gọi join(). Trong suốt thời gian này, nó ở trạng thái
WAITING cho đến khi luồng
t hoàn thành. Cũng có biến thể với thời hạn —
join(long millis). Khi đó luồng chờ một khoảng thời gian giới hạn và trạng thái của nó sẽ là
TIMED_WAITING.
Ngắt luồng: interrupt()
Có tình huống bạn cần lịch sự yêu cầu luồng kết thúc, ví dụ khi người dùng nhấn nút “Hủy”. Khi đó dùng phương thức interrupt():
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// Đang làm việc...
}
System.out.println("Luồng kết thúc do bị ngắt!");
});
t.start();
// ... sau một thời gian:
t.interrupt(); // Tín hiệu cho luồng: "đến lúc dừng lại"
Điều quan trọng cần hiểu là gọi interrupt() không giết luồng ngay lập tức. Nó chỉ đặt một cờ đặc biệt. Chính luồng đó cần định kỳ kiểm tra cờ này qua isInterrupted() và tự kết thúc. Nếu lúc đó luồng đang ở trạng thái ngủ (sleep()) hoặc chờ (wait()), nó không chỉ thấy cờ mà sẽ nhận ngay ngoại lệ InterruptedException. Đó chính là cách “đúng chuẩn” để dừng luồng trong Java: chương trình không cưỡng bức ngắt luồng, mà thông báo rằng đã đến lúc kết thúc.
3. Ví dụ về các chuyển trạng thái
Hãy xem qua các ví dụ về cách một luồng “di chuyển” qua các trạng thái của mình.
Ví dụ 1: NEW
→ RUNNABLE
→ TERMINATED
NEWRUNNABLETERMINATEDThread t = new Thread(() -> System.out.println("Xin chào!"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (hoặc TERMINATED, nếu luồng rất nhanh)
t.join();
System.out.println(t.getState()); // TERMINATED
Ví dụ 2: RUNNABLE
→ TIMED_WAITING
→ RUNNABLE
→ TERMINATED
RUNNABLETIMED_WAITINGRUNNABLETERMINATEDThread t = new Thread(() -> {
try {
System.out.println("Đang ngủ...");
Thread.sleep(1000); // TIMED_WAITING
System.out.println("Đã thức dậy!");
} catch (InterruptedException e) {
System.out.println("Luồng đã bị ngắt!");
}
});
t.start();
Ví dụ 3: RUNNABLE
→ WAITING
với join()
RUNNABLEWAITINGThread t1 = new Thread(() -> {
try { Thread.sleep(500); } catch (InterruptedException ignored) {}
System.out.println("t1 đã kết thúc");
});
Thread t2 = new Thread(() -> {
try {
t1.join(); // t2 chờ t1, ở trạng thái WAITING
System.out.println("t2 đã đợi xong t1");
} catch (InterruptedException ignored) {}
});
t1.start();
t2.start();
Ví dụ 4: BLOCKED
BLOCKEDObject lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
System.out.println("t1 đã thoát khỏi khối synchronized");
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2 đã vào synchronized");
}
});
t1.start();
Thread.sleep(100); // Cho t1 chiếm lock
t2.start();
Thread.sleep(100); // Cho t2 thử vào synchronized
System.out.println("Trạng thái của t2: " + t2.getState()); // BLOCKED
4. Làm sao biết trạng thái của luồng? Các phương thức isAlive() và getState()
- isAlive() — trả về true nếu luồng đã được khởi chạy và chưa kết thúc (
— khi đó là false).TERMINATED - getState() — trả về trạng thái hiện tại của luồng (một giá trị của enum Thread.State).
Thread t = new Thread(() -> {});
System.out.println(t.isAlive()); // false (NEW)
t.start();
System.out.println(t.isAlive()); // true (RUNNABLE/WAITING/...)
t.join();
System.out.println(t.isAlive()); // false (TERMINATED)
5. Vì sao không thể “giết” một luồng trực tiếp và các lời khuyên thực tiễn khác
Không có phương thức “kill”!
Trong Java không có phương thức cho phép “giết” luồng theo lệnh. Tại sao? Vì điều đó không an toàn: nếu luồng đang giữ tài nguyên nào đó (tệp, kết nối, khóa), việc ép hủy có thể khiến hệ thống rơi vào trạng thái không nhất quán.
Các phương thức lỗi thời: stop(), suspend(), resume()
Trong các phiên bản Java rất cũ có các phương thức stop(), suspend(), resume(). Giờ đây chúng được đánh dấu @Deprecated và tuyệt đối không khuyến nghị sử dụng. Vì sao?
- stop() có thể giết luồng ở bất kỳ thời điểm nào, để lại dữ liệu ở trạng thái không nhất quán.
- suspend() có thể “đóng băng” một luồng đang giữ khóa, khiến cả chương trình treo.
- resume() đôi khi không thể “rã băng” luồng nếu nó đã kết thúc.
Cách tiếp cận hiện đại:
Luồng nên tự kết thúc một cách chuẩn mực bằng cách phản hồi cờ ngắt (isInterrupted()) hoặc các tín hiệu khác.
Thực hành tốt
- Không gọi các phương thức đã bị coi là lỗi thời.
- Sử dụng cờ ngắt để dừng luồng (interrupt() và kiểm tra isInterrupted()).
- Theo dõi các trạng thái luồng khi gỡ lỗi — điều này giúp phát hiện treo và deadlock.
- Đừng quên join() nếu cần chờ luồng kết thúc công việc.
6. Lỗi thường gặp khi làm việc với vòng đời luồng
Lỗi số 1: Khởi chạy lại cùng một luồng.
Trong Java, một luồng chỉ có thể được khởi chạy một lần. Nếu gọi start() lần thứ hai — bạn sẽ nhận IllegalThreadStateException. Nếu cần lặp lại tác vụ — hãy tạo đối tượng Thread mới.
Thread t = new Thread(() -> {});
t.start();
t.start(); // Sẽ ném ngoại lệ!
Lỗi số 2: Nhầm lẫn giữa run() và start().
Gọi run() trực tiếp không chạy mã trong luồng mới — nó chạy trong luồng hiện tại (ví dụ main). Chỉ start() mới thực sự khởi chạy một luồng mới.
Lỗi số 3: Không xử lý InterruptedException.
Nếu luồng đang ngủ hoặc chờ và bị ngắt, InterruptedException sẽ được ném ra. Nếu bỏ qua, luồng có thể “ngủ mãi mãi” hoặc kết thúc bất ngờ.
Lỗi số 4: Không kiểm tra trạng thái của luồng.
Đôi khi chương trình bị treo vì một luồng đang chờ luồng khác vốn đã kết thúc hoặc sẽ chẳng bao giờ bắt đầu. Hãy dùng getState() và isAlive() để chẩn đoán.
Lỗi số 5: Dùng các phương thức điều khiển luồng lỗi thời.
Các phương thức stop(), suspend(), resume() là tệ. Đừng dùng chúng, kể cả khi rất muốn “sửa nhanh mọi thứ”.
GO TO FULL VERSION