Chờ Luồng
(Waiting Thread)

Phương thức join() cho phép một luồng chờ cho đến khi một luồng khác hoàn thành. Khi bạn gọi phương thức join() trên một đối tượng Thread, luồng hiện tại sẽ tạm dừng thực thi cho đến khi luồng của đối tượng Thread đó kết thúc.

Tuy nhiên, giống như phương thức sleep(), join cũng phụ thuộc vào hệ điều hành để tính thời gian, do đó, bạn không nên giả định rằng join sẽ chờ chính xác đúng như khoảng thời gian bạn chỉ định.

Phương thức join() có một vài phiên bản khác nhau, mỗi phiên bản cho phép bạn điều khiển thời gian chờ đợi của luồng một cách linh hoạt hơn. Dưới đây là 3 phiên bản khác nhau của phương thức join():

1. Phương thức void join()

↳ Khi phương thức join() được gọi, luồng hiện tại dừng thực thi và chuyển sang trạng thái chờ. Luồng hiện tại vẫn ở trạng thái chờ cho đến khi luồng mà phương thức join() được gọi kết thúc. Nếu luồng bị gián đoạn, nó sẽ ném ra ngoại lệ InterruptedException.

Cú pháp:

Cú pháp

public final void join() throws InterruptedException

Dưới đây là một ví dụ về cách sử dụng phương thức join() trong Java để chờ đợi một luồng khác kết thúc trước khi tiếp tục thực thi:

Ví dụ: Example.java

public class Example {

    public static void main(String[] args) {
        // Tạo hai đối tượng luồng
        Thread thread1 = new Thread(new SimpleTask("Task 1"));
        Thread thread2 = new Thread(new SimpleTask("Task 2"));

        // Bắt đầu thực thi hai luồng
        thread1.start();
        thread2.start();

        try {
            // Chờ đợi cho hai luồng hoàn thành
            thread1.join();  // Luồng chính chờ cho thread1 hoàn thành
            System.out.println("Thread 1 đã hoàn tất.");
            thread2.join();  // Luồng chính chờ cho thread2 hoàn thành
            System.out.println("Thread 2 đã hoàn tất.");
        } catch (InterruptedException e) {
            System.out.println("Thread main đã bị gián đoạn!!");
        }

        // Luồng chính tiếp tục thực hiện sau khi cả hai luồng hoàn thành
        System.out.println("Cả hai Thread đã hoàn tất. Thread main tiếp tục.");
    }
}

class SimpleTask implements Runnable {
    private String taskName;

    public SimpleTask(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(taskName + " đang hoạt động: " + i);
            try {
                Thread.sleep(1000); // Dừng lại 1 giây
            } catch (InterruptedException e) {
                System.out.println(taskName + " đã bị gián đoạn!");
            }
        }
    }
}

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

Task 1 đang hoạt động: 1
Task 2 đang hoạt động: 1
Task 2 đang hoạt động: 2
Task 1 đang hoạt động: 2
Task 1 đang hoạt động: 3
Task 2 đang hoạt động: 3
Thread 1 đã hoàn tất.
Thread 2 đã hoàn tất.
Cả hai Thread đã hoàn tất. Thread main tiếp tục.

Ví dụ trên minh họa cách sử dụng phương thức join() để đảm bảo rằng luồng chính sẽ không tiếp tục thực hiện công việc của nó cho đến khi luồng phụ đã hoàn thành công việc của nó.

2. Phương thức join(long millis)

↳ Khi phương thức join() được gọi, luồng hiện tại dừng thực thi và chuyển sang trạng thái chờ.

↳ Luồng hiện tại vẫn ở trạng thái chờ cho đến khi luồng mà phương thức join() được gọi kết thúc hoặc thời gian chờ đợi đã định (tính bằng mili giây) kết thúc.

↳ Nếu luồng bị gián đoạn, nó sẽ ném ra ngoại lệ InterruptedException.

Cú pháp:

Cú pháp

public final void join() throws InterruptedException

Dưới đây là một ví dụ về cách sử dụng phương thức join(long millis) để chờ đợi một khoảng thời gian nhất định trước khi tiếp tục thực hiện. Trong ví dụ này, chúng ta sẽ có ba luồng, và luồng chính sẽ chờ cho một khoảng thời gian cụ thể trước khi tiếp tục thực hiện.

Ví dụ: Example.java

public class Example {

    public static void main(String[] args) {
        // Tạo ba đối tượng luồng
        Thread thread1 = new Thread(new SimpleTask("Task 1"));
        Thread thread2 = new Thread(new SimpleTask("Task 2"));
        Thread thread3 = new Thread(new SimpleTask("Task 3"));

        // Bắt đầu thực thi các luồng
        thread1.start();
        thread2.start();
        thread3.start();

        try {
            // Chờ đợi tối đa 2000 mili giây (2 giây) cho mỗi luồng
            thread1.join(2000);  // Chờ tối đa 2 giây cho thread1 hoàn thành
            System.out.println("Thread 1 đã hoàn thành hoặc hết thời gian chờ..");
            
            thread2.join(2000);  // Chờ tối đa 2 giây cho thread2 hoàn thành
            System.out.println("Thread 2 đã hoàn thành hoặc hết thời gian chờ..");
            
            thread3.join(2000);  // Chờ tối đa 2 giây cho thread3 hoàn thành
            System.out.println("Thread 3 đã hoàn thành hoặc hết thời gian chờ.");
        } catch (InterruptedException e) {
            System.out.println("Main thread đã bị gián đoạn!");
        }

        // Luồng chính tiếp tục thực hiện sau khi thời gian chờ hết hạn hoặc các luồng đã hoàn thành
        System.out.println("Main thread tiếp tục sau khi hết thời gian chờ hoặc luồng hoàn thành..");
    }
}

class SimpleTask implements Runnable {
    private String taskName;

    public SimpleTask(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(taskName + " đang hoạt động: " + i);
            try {
                Thread.sleep(1000); // Dừng lại 1 giây
            } catch (InterruptedException e) {
                System.out.println(taskName + " đã bị gián đoạn!");
                return; // Thoát khỏi luồng nếu bị gián đoạn
            }
        }
    }
}

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

Task 3 đang hoạt động: 1
Task 2 đang hoạt động: 1
Task 1 đang hoạt động: 1
Task 3 đang hoạt động: 2
Task 1 đang hoạt động: 2
Task 2 đang hoạt động: 2
Thread 1 đã hoàn thành hoặc hết thời gian chờ..
Task 1 đang hoạt động: 3
Task 3 đang hoạt động: 3
Task 2 đang hoạt động: 3
Task 1 đang hoạt động: 4
Task 3 đang hoạt động: 4
Task 2 đang hoạt động: 4
Thread 2 đã hoàn thành hoặc hết thời gian chờ..
Task 1 đang hoạt động: 5
Task 2 đang hoạt động: 5
Task 3 đang hoạt động: 5
Thread 3 đã hoàn thành hoặc hết thời gian chờ.
Main thread tiếp tục sau khi hết thời gian chờ hoặc luồng hoàn thành..

Ví dụ này cho thấy cách sử dụng phương thức join(long millis) để chờ đợi một khoảng thời gian nhất định và xử lý tình huống khi các luồng có thể không hoàn thành trong thời gian chờ đợi.

3. Phương thức join(long mls, int nanos)

↳ Khi phương thức join() được gọi, luồng hiện tại dừng thực thi và chuyển sang trạng thái chờ.

↳ Luồng hiện tại vẫn ở trạng thái chờ cho đến khi luồng mà phương thức join() được gọi kết thúc hoặc thời gian chờ đợi đã định (tính bằng mili giây + nano giây) kết thúc.

↳ nanos: Thời gian chờ đợi thêm tính bằng nano giây (0 đến 999999).

↳ Nếu luồng bị gián đoạn, nó sẽ ném ra ngoại lệ InterruptedException.

Cú pháp:

Cú pháp

public final synchronized void join(long mls, int nanos) throws InterruptedException

Dưới đây là một ví dụ đơn giản về cách sử dụng phương thức join(long millis, int nanos) để chờ đợi một khoảng thời gian chính xác trước khi tiếp tục thực hiện.

Ví dụ: Example.java

public class Example {

    public static void main(String[] args) {
        // Tạo ba đối tượng luồng
        Thread thread1 = new Thread(new SimpleTask("Task 1"));
        Thread thread2 = new Thread(new SimpleTask("Task 2"));
        Thread thread3 = new Thread(new SimpleTask("Task 3"));

        // Bắt đầu thực thi các luồng
        thread1.start();
        thread2.start();
        thread3.start();

        try {
            // Chờ đợi tối đa 2 giây và 500 triệu nano giây (0.5 giây) cho mỗi luồng
            thread1.join(2000, 500000);  // Chờ tối đa 2 giây và 0.5 mili giây cho thread1 hoàn thành
            System.out.println("Thread 1 đã hoàn thành hoặc hết thời gian chờ.");
            
            thread2.join(2000, 500000);  // Chờ tối đa 2 giây và 0.5 mili giây cho thread2 hoàn thành
            System.out.println("Thread 2 đã hoàn thành hoặc hết thời gian chờ.");
        
            thread3.join(2000, 500000);  // Chờ tối đa 2 giây và 0.5 mili giây cho thread3 hoàn thành
            System.out.println("Thread 3 đã hoàn thành hoặc hết thời gian chờ.");
        } catch (InterruptedException e) {
            System.out.println("Main thread đã bị gián đoạn!");
        }

        // Luồng chính tiếp tục thực hiện sau khi thời gian chờ hết hạn hoặc các luồng đã hoàn thành
        System.out.println("Main thread tiếp tục sau khi hết thời gian chờ hoặc luồng hoàn thành...");
    }
}

class SimpleTask implements Runnable {
    private String taskName;

    public SimpleTask(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(taskName + " đang hoạt động: " + i);
            try {
                Thread.sleep(1000); // Dừng lại 1 giây
            } catch (InterruptedException e) {
                System.out.println(taskName + " đã bị gián đoạn!");
                return; // Thoát khỏi luồng nếu bị gián đoạn
            }
        }
    }
}

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

Task 3 đang hoạt động: 1
Task 2 đang hoạt động: 1
Task 1 đang hoạt động: 1
Task 3 đang hoạt động: 2
Task 2 đang hoạt động: 2
Task 1 đang hoạt động: 2
Thread 1 đã hoàn thành hoặc hết thời gian chờ.
Task 3 đang hoạt động: 3
Task 2 đang hoạt động: 3
Task 1 đang hoạt động: 3
Task 3 đang hoạt động: 4
Task 1 đang hoạt động: 4
Task 2 đang hoạt động: 4
Thread 2 đã hoàn thành hoặc hết thời gian chờ.
Task 3 đang hoạt động: 5
Task 2 đang hoạt động: 5
Task 1 đang hoạt động: 5
Thread 3 đã hoàn thành hoặc hết thời gian chờ.
Main thread tiếp tục sau khi hết thời gian chờ hoặc luồng hoàn thành...

Ví dụ này cho thấy cách sử dụng phương thức join(long millis, int nanos) để chờ đợi một khoảng thời gian chính xác hơn và xử lý tình huống khi các luồng có thể không hoàn thành trong thời gian chờ đợi.

Ví dụ

Dưới đây là ví dụ về cách tạo một luồng khác để gián đoạn luồng chính khi nó đang chờ một luồng khác hoàn thành, từ đó bạn có thể thấy InterruptedException được ném ra.

Ví dụ: Example.java

class InterruptTask extends Thread {
    private Thread threadToInterrupt;

    // Gán luồng cần gián đoạn trong phương thức này
    public void setThreadToInterrupt(Thread thread) {
        this.threadToInterrupt = thread;
    }

    @Override
    public void run() {
        // Gián đoạn luồng được chỉ định
        threadToInterrupt.interrupt();
    }
}

public class Example {
    public static void main(String[] args) {
        try {
            // Tạo một đối tượng luồng phụ
            Thread mainThread = Thread.currentThread();
            
            // Tạo đối tượng của luồng InterruptTask
            InterruptTask interruptTask = new InterruptTask();
            interruptTask.setThreadToInterrupt(mainThread); // Gán luồng chính để gián đoạn
            interruptTask.start(); // Bắt đầu luồng InterruptTask
            
            // Chờ luồng InterruptTask hoàn thành hoặc bị gián đoạn
            interruptTask.join(); 
        } catch (InterruptedException ex) {
            System.out.println("Luồng chính đã bị gián đoạn! Ngoại lệ: " + ex);
        }

        System.out.println("Luồng chính tiếp tục sau khi luồng InterruptTask hoàn thành hoặc bị gián đoạn.");
    }
}

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

Luồng chính đã bị gián đoạn! Ngoại lệ: java.lang.InterruptedException
Luồng chính tiếp tục sau khi luồng InterruptTask hoàn thành hoặc bị gián đoạn.

Hy vọng rằng qua các ví dụ trên, bạn sẽ hiểu rõ hơn về cách ngắt luồng trong Java.

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.”