Trạng Thái Của Thread
(Thread States)

Trong Java, một luồng luôn tồn tại trong một trong các trạng thái sau đây:

1. Mới (New)

2. Hoạt động (Active)

3. Bị chặn / Đang chờ (Blocked / Waiting)

4. Chờ có thời gian (Timed Waiting)

5. Kết thúc (Terminated)

Giải thích các trạng thái luồng (Thread States)

1. Mới (New)

↳ Khi một luồng mới được tạo ra, nó luôn ở trạng thái mới. Ở trạng thái này, mã lệnh của luồng chưa được thực thi và luồng chưa bắt đầu công việc của nó.

2. Hoạt động (Active)

↳ Khi một luồng gọi phương thức start(), nó chuyển từ trạng thái mới sang trạng thái hoạt động. Trạng thái hoạt động bao gồm hai trạng thái con: một là sẵn sàng (runnable), và một là đang chạy (running).

↳ Sẵn sàng (Runnable): Một luồng sẵn sàng để chạy sẽ chuyển sang trạng thái sẵn sàng. Trong trạng thái sẵn sàng, luồng có thể đang chạy hoặc có thể sẵn sàng chạy bất kỳ lúc nào. Nhiệm vụ của bộ lập lịch luồng là cung cấp thời gian cho luồng chạy, tức là chuyển luồng sang trạng thái đang chạy.

↳ Một chương trình đa luồng được cấp một khoảng thời gian cố định cho mỗi luồng. Mỗi luồng chạy trong một khoảng thời gian ngắn và khi khoảng thời gian đó kết thúc, luồng sẽ tự nguyện nhường CPU cho luồng khác, để các luồng khác cũng có cơ hội thực thi. Khi xảy ra tình huống này, tất cả các luồng sẵn sàng chờ đợi lượt của mình đều nằm trong trạng thái sẵn sàng. Trong trạng thái sẵn sàng, có một hàng đợi chứa các luồng.

↳ Đang chạy (Running): Khi luồng nhận được CPU, nó chuyển từ trạng thái sẵn sàng sang trạng thái đang chạy. Thay đổi trạng thái của một luồng từ sẵn sàng sang đang chạy và quay lại trạng thái sẵn sàng là rất phổ biến.

3. Bị chặn hoặc đang chờ (Blocked / Waiting)

↳ Khi một luồng không hoạt động trong một khoảng thời gian (không phải vĩnh viễn), thì nó có thể ở trạng thái bị chặn hoặc đang chờ.

↳ Ví dụ, một luồng (giả sử là A) muốn in dữ liệu từ máy in. Tuy nhiên, trong khi đó, một luồng khác (giả sử là B) đang sử dụng máy in. Vì vậy, luồng A phải chờ luồng B hoàn thành việc sử dụng máy in. Do đó, luồng A ở trạng thái bị chặn. Một luồng ở trạng thái bị chặn không thể thực thi và không tiêu tốn tài nguyên CPU. Luồng A sẽ giữ trạng thái bị chặn cho đến khi bộ lập lịch kích hoạt lại nó.

↳ Khi luồng chính gọi phương thức join(), nó sẽ ở trạng thái chờ. Luồng chính sẽ chờ các luồng con hoàn thành công việc của chúng. Khi các luồng con hoàn tất, một thông báo sẽ được gửi đến luồng chính, và luồng chính sẽ chuyển từ trạng thái chờ sang trạng thái hoạt động.

↳ Nếu có nhiều luồng ở trạng thái chờ hoặc bị chặn, bộ lập lịch có trách nhiệm quyết định luồng nào sẽ được chọn để thực thi và luồng nào sẽ bị từ chối. Luồng được chọn sẽ có cơ hội để chạy.

4. Chờ có thời gian (Timed Waiting)

↳ Đôi khi, việc chờ đợi có thể dẫn đến hiện tượng đói tài nguyên (starvation). Ví dụ, một luồng (gọi là A) đã vào một đoạn mã quan trọng và không chịu rời khỏi đó. Trong trường hợp này, một luồng khác (gọi là B) phải chờ mãi mãi, dẫn đến đói tài nguyên. Để tránh điều này, trạng thái chờ có thời gian được cấp cho luồng B. Luồng sẽ chờ trong một khoảng thời gian cụ thể, chứ không phải mãi mãi. Một ví dụ thực tế của chờ có thời gian là khi chúng ta gọi phương thức sleep() trên một luồng. Phương thức sleep() đặt luồng vào trạng thái chờ có thời gian. Sau khi thời gian kết thúc, luồng sẽ tỉnh dậy và tiếp tục thực thi từ điểm mà nó đã rời khỏi trước đó.

5. Kết thúc (Terminated)

Một luồng đạt trạng thái kết thúc vì các lý do sau:

↳ Khi một luồng hoàn thành công việc của nó, nó kết thúc hoặc kết thúc bình thường.

↳ Kết thúc bất thường: Xảy ra khi có các sự kiện không bình thường như ngoại lệ không được xử lý hoặc lỗi phân đoạn.

↳ Một luồng đã kết thúc có nghĩa là luồng đó không còn tồn tại trong hệ thống nữa. Nói cách khác, luồng đó đã chết và không thể phục hồi (hoạt động lại sau khi bị hủy).

Trạng thái Luồng (Thread States) trong Java - minh họa
Ảnh mô tả trạng thái Luồng.

Triển khai các trạng thái luồng (Thread States)

Trong Java, bạn có thể lấy trạng thái hiện tại của một luồng bằng cách sử dụng phương thức Thread.getState(). Lớp java.lang.Thread.State cung cấp các hằng ENUM để đại diện cho các trạng thái của một luồng. Các hằng này bao gồm:

↳ public static final Thread.State NEW: Đại diện cho trạng thái đầu tiên của một luồng, tức là trạng thái MỚI (NEW).

↳ public static final Thread.State RUNNABLE: Đại diện cho trạng thái sẵn sàng (RUNNABLE). Điều này có nghĩa là một luồng đang chờ trong hàng đợi để được thực thi

↳ public static final Thread.State BLOCKED: Đại diện cho trạng thái bị chặn (BLOCKED). Trong trạng thái này, luồng đang chờ để có được một khóa.

↳ public static final Thread.State WAITING: Đại diện cho trạng thái đang chờ (WAITING). Một luồng sẽ vào trạng thái này khi gọi phương thức Object.wait() hoặc phương thức Thread.join() mà không có thời gian giới hạn. Một luồng trong trạng thái chờ đang chờ một luồng khác hoàn thành công việc của nó.

↳ public static final Thread.State TIMED_WAITING: Đại diện cho trạng thái chờ có thời gian (TIMED_WAITING). Sự khác biệt chính giữa trạng thái chờ và chờ có thời gian là ràng buộc về thời gian. Trạng thái chờ không có ràng buộc về thời gian, trong khi trạng thái chờ có thời gian có ràng buộc về thời gian. Một luồng sẽ vào trạng thái chờ có thời gian khi gọi các phương thức sau:

↳ sleep

↳ join với thời gian chờ

↳ wait với thời gian chờ

↳ parkUntil

↳ parkNanos

↳ public static final Thread.State TERMINATED: Đại diện cho trạng thái cuối cùng của một luồng, tức là trạng thái đã kết thúc (TERMINATED) hoặc đã chết. Một luồng kết thúc có nghĩa là nó đã hoàn thành việc thực thi của mình.

Các hằng ENUM này giúp bạn dễ dàng kiểm tra và xử lý các trạng thái của luồng trong chương trình Java của bạn.

Ví dụ

Dưới đây là một ví dụ về chương trình Java để trình diễn các trạng thái khác nhau của một luồng (thread):

Ví dụ: Example.java

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Luồng đang ở trạng thái: RUNNABLE");

        // Để luồng ngủ trong 2 giây để minh họa trạng thái TIMED_WAITING
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Luồng đã kết thúc thực thi: TERMINATED");
    }
}

public class Example {
    public static void main(String[] args) {
        // Tạo một đối tượng luồng
        MyThread thread = new MyThread();

        // Kiểm tra trạng thái của luồng trước khi bắt đầu
        System.out.println("Trạng thái của luồng trước khi bắt đầu: " + thread.getState());

        // Bắt đầu luồng
        thread.start();

        // Kiểm tra trạng thái của luồng sau khi bắt đầu
        System.out.println("Trạng thái của luồng sau khi bắt đầu: " + thread.getState());

        // Đợi một chút để chắc chắn rằng luồng đã chuyển sang trạng thái RUNNABLE
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Kiểm tra trạng thái của luồng trong khi nó đang ngủ
        System.out.println("Trạng thái của luồng trong khi đang ngủ: " + thread.getState());

        // Đợi luồng kết thúc
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Kiểm tra trạng thái của luồng sau khi kết thúc
        System.out.println("Trạng thái của luồng sau khi kết thúc: " + thread.getState());
    }
}

Kết quả của chương trình là

Trạng thái của luồng trước khi bắt đầu: NEW
Trạng thái của luồng sau khi bắt đầu: RUNNABLE
Luồng đang ở trạng thái: RUNNABLE
Trạng thái của luồng trong khi đang ngủ: TIMED_WAITING
Luồng đã kết thúc thực thi: TERMINATED
Trạng thái của luồng sau khi kết thúc: TERMINATED

Trong ví dụ này, chương trình Java minh họa các trạng thái chính của luồng từ khi được tạo ra, bắt đầu, ngủ, và kết thúc.

Câu Nói Truyền Cảm Hứng

“Bắt đầu ở đâu không quan trọng, quan trọng là bạn sẵn sàng bắt đầu.” – W. Clement Stone

Không Gian Tích Cực

“Chúc bạn luôn giữ vững niềm tin và sức mạnh để vượt qua mọi thử thách trong cuộc sống.”