Skip to content

Thread Lifecycle Trong Java

Khi học Java Multithreading, hầu như ai cũng biết Thread có 6 trạng thái:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

Tuy nhiên, trong thực tế làm việc, việc chỉ nhớ tên các trạng thái gần như không giúp ích nhiều.

Điều quan trọng hơn là:

  • Khi nào thread chuyển sang từng trạng thái?
  • JVM làm gì trong mỗi trạng thái?
  • Hệ điều hành tham gia như thế nào?
  • Làm sao đọc thread dump để tìm bottleneck trên production?

Trong bài viết này, chúng ta sẽ đi từ kiến thức nền tảng đến các tình huống thực tế mà một Senior Java Engineer thường gặp.

Thread là gì?

Thread là đơn vị thực thi nhỏ nhất trong một process.

Một JVM process có thể chứa hàng trăm hoặc hàng nghìn thread chạy đồng thời.

Mỗi thread có tài nguyên riêng:

  • Program Counter (PC Register)
  • Java Stack
  • Native Stack
  • Thread State

Trong khi đó các thread cùng chia sẻ:

  • Heap
  • Method Area
  • Runtime Constant Pool

Chính việc chia sẻ Heap khiến lập trình đa luồng trở nên phức tạp vì nhiều thread có thể truy cập cùng một object.

Java Thread Lifecycle

Java định nghĩa vòng đời của thread thông qua enum Thread.State.

public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED
}

Lưu ý rằng Java không có trạng thái RUNNING.

Đây là điểm khiến nhiều lập trình viên nhầm lẫn.

Trong JVM, thread đang chạy trên CPU và thread đang chờ CPU đều được xem là RUNNABLE.

Việc thread nào thực sự được CPU thực thi hoàn toàn do Operating System Scheduler quyết định.

NEW

Đây là trạng thái đơn giản nhất.

Thread thread = new Thread(() -> {
System.out.println("Hello");
});

Lúc này:

thread.getState();

Kết quả:

NEW

Thread mới chỉ tồn tại dưới dạng object.

JVM chưa tạo native thread.

Chưa có stack thực thi.

Thread chưa được đăng ký với Operating System Scheduler.

Điều gì xảy ra khi gọi start()

Nhiều người nghĩ:

thread.start();

nghĩa là thread sẽ chạy ngay.

Điều này không chính xác.

Sau khi gọi start():

  • JVM tạo native thread.
  • Cấp phát stack.
  • Đăng ký với OS Scheduler.
  • Thread chuyển sang RUNNABLE.

Việc có được CPU hay không phụ thuộc hoàn toàn vào Scheduler của hệ điều hành.

Do đó hai thread được start() liên tiếp hoàn toàn có thể chạy theo bất kỳ thứ tự nào.

RUNNABLE

Đây là trạng thái dễ gây nhầm nhất.

Trong Java:

RUNNABLE bao gồm cả:

  • đang chờ CPU
  • đang chạy trên CPU

Ví dụ:

while (true) {

}

Thread luôn ở trạng thái:

RUNNABLE

Ngay cả khi CPU đang thực sự thực thi vòng lặp đó.

Java không cung cấp trạng thái RUNNING riêng biệt.

CPU Scheduler và Context Switching

Giả sử máy chỉ có 2 CPU Core nhưng có 100 thread.

Scheduler sẽ liên tục:

  • chọn thread
  • lưu register
  • lưu Program Counter
  • chuyển sang thread khác

Quá trình này gọi là Context Switching.

Context switching quá nhiều sẽ làm giảm hiệu năng do CPU phải liên tục lưu và khôi phục trạng thái của thread thay vì thực thi business logic.

Đây là một trong những lý do Virtual Thread xuất hiện trong Java 21 để giảm chi phí quản lý thread ở tầng ứng dụng, dù việc lập lịch vẫn có sự phối hợp với hệ điều hành.

BLOCKED

BLOCKED xảy ra khi một thread muốn lấy monitor lock nhưng lock đang bị thread khác giữ.

Trạng thái này chỉ xuất hiện với synchronized.

Ví dụ:

public class BlockedDemo {

private static final Object LOCK = new Object();

public static void main(String[] args) throws Exception {

Thread t1 = new Thread(() -> {
synchronized (LOCK) {

System.out.println("Thread-1 acquired lock");

try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}

System.out.println("Thread-1 released lock");
}
});

Thread t2 = new Thread(() -> {

synchronized (LOCK) {
System.out.println("Thread-2 acquired lock");
}

});

t1.start();

Thread.sleep(500);

t2.start();

Thread.sleep(500);

System.out.println(t1.getState());

System.out.println(t2.getState());

}

}

Output:

Thread-1 acquired lock

TIMED_WAITING

BLOCKED

Thread-1 released lock

Thread-2 acquired lock

Điều đáng chú ý là:

Mặc dù Thread-1 đang sleep(), nó vẫn giữ monitor lock.

Do đó Thread-2 không thể đi vào synchronized và phải chuyển sang BLOCKED.

Đây là lỗi hiểu nhầm rất phổ biến khi mới học Java Concurrency.

WAITING

WAITING xảy ra khi thread chủ động tạm dừng và chờ một sự kiện từ thread khác.

Ví dụ:

thread.join();

hoặc

object.wait();

hoặc

LockSupport.park();

Khác với BLOCKED, thread trong WAITING không chờ để lấy lock mà đang chờ một tín hiệu như:

  • notify()
  • notifyAll()
  • unpark()
  • thread được join() kết thúc

TIMED_WAITING

Đây là phiên bản có timeout của WAITING.

Ví dụ:

Thread.sleep(5000);

hoặc

thread.join(5000);

hoặc

object.wait(3000);

Sau khi timeout kết thúc, thread sẽ quay lại RUNNABLE.

TERMINATED

Khi phương thức run() kết thúc hoặc exception không được bắt, thread sẽ chuyển sang TERMINATED.

Ví dụ:

Thread thread = new Thread(() -> {

System.out.println("Finished");

});

thread.start();

thread.join();

System.out.println(thread.getState());

Output:

TERMINATED

Một thread đã kết thúc không thể được start() lần thứ hai.

Nếu gọi lại:

thread.start();

JVM sẽ ném:

IllegalThreadStateException

BLOCKED và WAITING khác nhau như thế nào?

Đây là câu hỏi rất phổ biến trong các buổi phỏng vấn Senior.

BLOCKEDWAITING
Chờ monitor lockChờ một tín hiệu từ thread khác
Chỉ xảy ra với synchronizedwait(), join(), park()
Không thể tự tiếp tụcĐược đánh thức bởi notify(), unpark(), hoặc khi thread được join kết thúc

Có thể ghi nhớ đơn giản:

BLOCKED = chờ lock

WAITING = chờ event

ReentrantLock có tạo ra BLOCKED không?

Không.

Đây là điểm rất nhiều lập trình viên nhầm.

Ví dụ:

lock.lock();

Nếu lock chưa lấy được, thread không chuyển sang BLOCKED.

Thay vào đó, thread sẽ được quản lý bởi AbstractQueuedSynchronizer (AQS) và thường ở trạng thái WAITING hoặc TIMED_WAITING tùy cách sử dụng (lockInterruptibly()tryLock(timeout)…).

Nói cách khác:

  • BLOCKED gần như gắn với monitor lock (synchronized).
  • Các lock trong java.util.concurrent.locks sử dụng cơ chế khác.

Đọc Thread Dump trên Production

Khi ứng dụng có dấu hiệu treo hoặc phản hồi chậm, một trong những bước đầu tiên là lấy thread dump:

jstack <pid>

Ví dụ:

"Thread-18"

java.lang.Thread.State: BLOCKED (on object monitor)

at com.demo.PaymentService.transfer(PaymentService.java:82)

- waiting to lock <0x0000000712345678>

- locked <0x0000000711111111>

Từ thread dump, bạn có thể biết:

  • Thread đang ở trạng thái nào.
  • Thread đang chờ monitor nào.
  • Thread nào đang giữ monitor đó.
  • Dòng code cụ thể gây lock contention.

Đây là kỹ năng rất quan trọng khi phân tích các vấn đề về deadlock, lock contention hoặc latency trong hệ thống có TPS cao.

Những hiểu lầm phổ biến

1. RUNNABLE nghĩa là đang chạy

Sai.

RUNNABLE bao gồm cả thread đang chạy và thread đang sẵn sàng chạy.

2. sleep() sẽ giải phóng lock

Sai.

Thread.sleep() chỉ đưa thread vào TIMED_WAITING.

Nếu thread đang ở trong synchronized, monitor lock vẫn được giữ.

3. wait() và sleep() giống nhau

Không.

wait() giải phóng monitor lock và chờ được đánh thức.

sleep() không giải phóng monitor lock, chỉ tạm dừng thực thi trong một khoảng thời gian.

4. interrupt() sẽ dừng thread ngay lập tức

Không.

interrupt() chỉ gửi tín hiệu ngắt. Thread có dừng hay không phụ thuộc vào việc nó có kiểm tra interrupt flag hoặc đang ở một thao tác blocking hỗ trợ interrupt hay không.

5. start() có thể gọi nhiều lần

Sai.

Một instance của Thread chỉ có thể được khởi động một lần. Muốn chạy lại, bạn phải tạo một thread mới.

Kết luận

Thread Lifecycle là nền tảng của lập trình đồng thời trong Java, nhưng giá trị thực sự không nằm ở việc thuộc lòng sáu trạng thái.

Điều quan trọng là hiểu vì sao thread chuyển trạng tháiJVM và hệ điều hành phối hợp như thế nào, và cách những trạng thái đó xuất hiện trong thread dump khi hệ thống gặp sự cố.

Khi bạn có thể nhìn vào một jstack và nhanh chóng nhận ra thread đang BLOCKED vì lock contention, hay đang WAITINGdo join() hoặc park(), bạn không chỉ nắm được lý thuyết mà còn có khả năng chẩn đoán và tối ưu các hệ thống Java chạy ở quy mô lớn. Đây cũng là kỹ năng thường được kỳ vọng ở các Senior Java Engineer trong môi trường production.

Reference

Thread-Lifecycle

Published inAll

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *