杰瑞科技汇

Java线程join方法如何等待其他线程结束?

Of course! Let's dive deep into the Thread.join() method in Java. It's a fundamental and very useful method for coordinating threads.

Java线程join方法如何等待其他线程结束?-图1
(图片来源网络,侵删)

What is Thread.join()?

In simple terms, Thread.join() is a method that makes the current thread (the one calling join()) wait until the thread it's called on terminates.

Think of it like waiting for a friend to finish their meal before you leave the restaurant.

  • You are the "current thread".
  • Your friend is the "target thread".
  • Calling friend.join() means you will pause and wait right there until your friend has finished eating (their run() method has completed).

The Core Concept: Thread Synchronization

When you call threadA.join(), you are essentially telling the main thread (or any other thread), "Don't proceed until threadA is completely done." This is a form of thread synchronization. It ensures that the work done by the target thread is visible to the waiting thread.


Method Signatures and Overloads

The join() method comes in three flavors, which give you different levels of control:

Java线程join方法如何等待其他线程结束?-图2
(图片来源网络,侵删)

join()

This is the simplest form. It waits indefinitely until the thread terminates.

Syntax:

public final void join() throws InterruptedException

Example:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("MyRunnable count: " + i);
            try {
                Thread.sleep(500); // Simulate work
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class SimpleJoinExample {
    public static void main(String[] args) {
        Thread myThread = new Thread(new MyRunnable());
        myThread.start();
        System.out.println("Main thread is about to wait for myThread.");
        try {
            myThread.join(); // Main thread pauses here until myThread finishes
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread has resumed after myThread finished.");
    }
}

Output:

Java线程join方法如何等待其他线程结束?-图3
(图片来源网络,侵删)
Main thread is about to wait for myThread.
MyRunnable count: 1
MyRunnable count: 2
MyRunnable count: 3
MyRunnable count: 4
MyRunnable count: 5
Main thread has resumed after myThread finished.

Notice how "Main thread has resumed..." only appears after MyRunnable has counted to 5.

join(long millis)

This form waits for the thread to terminate, but only for a specified number of milliseconds. If the thread doesn't finish in that time, the join() method simply returns, and the waiting thread resumes its execution.

Syntax:

public final void join(long millis) throws InterruptedException

Example:

public class TimedJoinExample {
    public static void main(String[] args) {
        Thread myThread = new Thread(() -> {
            try {
                System.out.println("Task thread is starting a 3-second task...");
                Thread.sleep(3000); // Task takes 3 seconds
                System.out.println("Task thread finished.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        myThread.start();
        System.out.println("Main thread is waiting for 2 seconds.");
        try {
            // Wait for a maximum of 2 seconds
            myThread.join(2000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread has resumed. The task may or may not be finished.");
    }
}

Output:

Main thread is waiting for 2 seconds.
Task thread is starting a 3-second task...
Main thread has resumed. The task may or may not be finished.
Task thread finished.

The main thread waited for 2 seconds, gave up, and continued, even though the task thread was still busy for one more second.

join(long millis, int nanos)

This is the most precise form. It waits for a specified number of milliseconds + nanoseconds.

Syntax:

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

This is useful for very high-precision timing, but in practice, join(long millis) is used far more often.


Handling InterruptedException

The join() method throws an InterruptedException. This is a crucial part of Java's cooperative thread cancellation mechanism.

  • When does it happen? If the thread that is waiting (the one that called join()) is interrupted by another thread, the join() call will throw this exception.
  • What should you do? You should catch the exception and decide how to handle it. Typically, you should re-set the thread's interrupted status by calling Thread.currentThread().interrupt() if you are not exiting the method.

Example with Interruption:

public class InterruptedJoinExample {
    public static void main(String[] args) {
        Thread longRunningThread = new Thread(() -> {
            try {
                System.out.println("Long-running thread started.");
                Thread.sleep(5000); // Sleep for 5 seconds
                System.out.println("Long-running thread finished.");
            } catch (InterruptedException e) {
                System.out.println("Long-running thread was interrupted!");
                // Restore the interrupted status
                Thread.currentThread().interrupt();
            }
        });
        longRunningThread.start();
        // Let the main thread wait for 1 second
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main thread is interrupting the long-running thread.");
        longRunningThread.interrupt(); // Interrupt the other thread
        try {
            // Now, join will be interrupted
            longRunningThread.join();
        } catch (InterruptedException e) {
            System.out.println("Main thread's join() was interrupted!");
            // Important: Restore the interrupted status for the main thread
            Thread.currentThread().interrupt();
        }
    }
}

Output:

Long-running thread started.
Main thread is interrupting the long-running thread.
Long-running thread was interrupted!
Main thread's join() was interrupted!

Common Use Cases

  1. Parallel Processing / Aggregation: This is the most common use case. You start several worker threads to perform sub-tasks in parallel and then use join() to wait for all of them to complete before proceeding to aggregate the results.

    public class ParallelSum {
        public static void main(String[] args) throws InterruptedException {
            int[] numbers = new int[1000000];
            // ... populate array ...
            int mid = numbers.length / 2;
            Thread sumThread1 = new Thread(() -> {
                // Sum first half
            });
            Thread sumThread2 = new Thread(() -> {
                // Sum second half
            });
            sumThread1.start();
            sumThread2.start();
            // Wait for both threads to finish
            sumThread1.join();
            sumThread2.join();
            // Now you can safely get the results from both threads
            // and calculate the total sum.
            System.out.println("Total sum calculated.");
        }
    }
  2. Ensuring Order of Execution: Sometimes you need to ensure one thread completes before another starts. join() is a simple way to enforce this sequence.

  3. Simplifying Unit Tests: When testing code that uses threads, you often need to wait for the thread's logic to complete before you can assert the results. join() is a convenient way to do this.


join() vs. ExecutorService

While Thread.join() works perfectly fine, modern Java code often uses the java.util.concurrent package, specifically the ExecutorService. It provides a more powerful and flexible way to manage threads.

The equivalent of waiting for all tasks to finish with an ExecutorService is the awaitTermination() method.

ExecutorService Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ExecutorServiceExample {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        System.out.println("Submitting tasks...");
        executor.submit(() -> {
            System.out.println("Task 1 running...");
            try { Thread.sleep(2000); } catch (InterruptedException e) {}
            System.out.println("Task 1 done.");
        });
        executor.submit(() -> {
            System.out.println("Task 2 running...");
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            System.out.println("Task 2 done.");
        });
        System.out.println("Tasks submitted. Shutting down executor...");
        executor.shutdown(); // Disable new tasks from being submitted
        // Wait for a maximum of 5 seconds for all tasks to finish
        if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
            System.out.println("Tasks did not finish in time. Forcing shutdown.");
            executor.shutdownNow(); // Cancel currently executing tasks
        }
        System.out.println("All tasks are done. Main thread can proceed.");
    }
}

Why prefer ExecutorService?

  • Thread Reuse: It uses a pool of threads, avoiding the overhead of creating a new thread for every task.
  • Lifecycle Management: It provides clean methods for starting (submit) and stopping (shutdown, shutdownNow).
  • Rich API: Offers features like Future for getting results, scheduling, and more complex concurrency control.

Summary

Feature Thread.join() ExecutorService.awaitTermination()
Purpose Wait for a specific thread to finish. Wait for all tasks in the executor to finish.
Granularity Fine-grained (per thread). Coarse-grained (per executor).
Modern Approach Considered a lower-level, legacy approach. The standard, modern, and recommended approach.
Use Case Simple scripts, legacy code, or when you need to wait for a single, identifiable thread. Almost all new applications involving concurrent tasks.
分享:
扫描分享到社交APP
上一篇
下一篇