Thread.join() 初探

208 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

使用场景

1. 主线程启动并创建子线程,如果子线程中要进行大量的耗时运算,主线程可能早于子线程结束。
2. 如果主线程需要知道子线程的执行结果时,就需要等待子线程执行结束。
3. 主线程可以使用sleep(xxx),但是这样等待的时间不好确定,因为子线程的执行时间不好确定。在这个场景中,join()就比较合适了。

主线程等待子线程的终止。也就是说,在主线程的代码块中,如果遇到了 Thread.join() 方法,此时主线程需要阻塞,等待子线程结束才能继续执行 Thread.join() 之后的代码块

详解

join() 方法是通过 wait() 实现的。

当主线程调用 join() 的时候,主线程会拿到子线程对象的锁,调用该对象的 wait() 方法,直到该对象唤醒主进程。

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
		// 超时时间等于0,意味着等待该线程结束没有时间限制
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

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

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        join(millis);
    }

   
    public final void join() throws InterruptedException {
        join(0);
    }

从源码中可以看出

  1. join()、join(0)是等价的,它们最终都调用了 Object.wait(0) 方法,而Object.wait(0) 方法是一直等待的,直到被 notify / 中断才会返回

  2. join() 和 sleep() 一样都可以被中断,中断时会抛出 InterruptedException 异常。不同的是,join() 内部调用了 wait(),会让出锁,而 sleep() 会一直保持锁。

注意

  1. join() 方法必须在线程 start() 方法调用之后才有意义。
  2. 在 join() 过程中,如果当前线程被中断,则当前线程出现异常。A线程调用了B线程的join()方法,A线程中断会报异常而B线程不会中断。例:
public class InterruptTest {
    public static void main(String[] args) throws Exception{
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                BThread bThread = new BThread();
                bThread.start();
                String name = Thread.currentThread().getName();
                System.out.println(name + " 开始执行");
                try {
                    Thread.sleep(2000);
                    Thread.currentThread().interrupt();
                    bThread.join();
                } catch (InterruptedException e) {
                    System.out.println(name + " 中断");
                }
                System.out.println(name + " 结束");
            }
        },"[NThread] Thread");
        thread.start();
        thread.join();
    }
}

public class BThread extends Thread{
    public BThread(){
        super("[BThread] Thread");
    }
    @Override
    public void run(){
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " 开始执行");
        try {
            for (int i = 0;i < 10;i++){
                System.out.println(threadName + " loop at " + i);
                Thread.sleep(1000);
            }
            System.out.println(threadName + " 结束" );
        }catch (Exception e){
            System.out.print(threadName);
            e.printStackTrace();
        }
    }
}

执行结果

[NThread] Thread 开始执行
[BThread] Thread 开始执行
[BThread] Thread loop at 0
[BThread] Thread loop at 1
[BThread] Thread loop at 2
[NThread] Thread 中断
[NThread] Thread 结束
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread loop at 5
[BThread] Thread loop at 6
[BThread] Thread loop at 7
[BThread] Thread loop at 8
[BThread] Thread loop at 9
[BThread] Thread 结束