文章4:join()

86 阅读4分钟

join() 方法的作用是同步。在主线程中去创建并启动一个线程,再调用这个线程的 join() 方法之后,会使得两个线程原本是并行关系变成串行关系,也就是主线程将会等待子线程执行完毕之后再继续执行。

注意:join() 方法可以传入一个 long 类型的参数,表示过了多少毫秒之后两个线程将由串行关系再次转变成并行关系。但如果传入的参数是0的话,表示的是永久等待,也就是主线程将会等待直到子线程执行完毕之后再次执行,相当于不传参数的 join() 方法。

代码演示:

public class Test implements Runnable {

    @Override
    public void run() {
        System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Test());
        Thread thread2 = new Thread(new Test());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("所有子线程执行完毕");
    }
}

// main方法执行结果:
// 子线程Thread-0开始执行
// 子线程Thread-1开始执行
// 子线程Thread-0执行完毕
// 子线程Thread-1执行完毕
// 所有子线程执行完毕

上面的代码如果将两个线程执行join方法的那行代码注释掉,则执行结果为

所有子线程执行完毕
子线程Thread-0开始执行
子线程Thread-1开始执行
子线程Thread-1执行完毕
子线程Thread-0执行完毕

很明显,join方法的调用会使得主线程去等待子线程执行完毕之后再重新执行代码。

join期间被中断

如果主线程调用子线程的 join() 方法后,在子线程执行的期间,有 interrupt 通知进入了,怎么办?

针对上面的问题,我再重申一下关于 join() 方法作用的介绍。主线程将会等待调用了 join() 方法的子线程执行完毕后再继续执行

实际上,是主线程在等待子线程执行完毕,也就是说陷入阻塞状态的是主线程而不是子线程。所以关于上面的问题如果有 interrupt 通知进入了主线程将会抛出一个 InterruptedException 来响应这个 interrupt 通知。

static Thread mainThread = Thread.currentThread();

public static void main(String[] args) {
    Thread thread = new Thread(() -> {
        mainThread.interrupt();
    });
    thread.start();

    try {
        thread.join();
    } catch (InterruptedException e) {
        System.out.println(Thread.currentThread().getName() + "线程被中断了");
    }
}

// main方法执行结果
main线程被中断了

启动一个子线程并调用 join() 方法,这时主线程就在等待子线程的执行完毕,然后子线程去中断了主线程。也就是中断了一个正在因join方法陷入阻塞的线程,那么此时我们中断的是主线程,而不是正在执行的子线程。

join期间的线程状态

static Thread mainThread = Thread.currentThread();

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        System.out.println(mainThread.getState());
        System.out.println(Thread.currentThread().getState());
    });
    thread.start();
    thread.join();
}

// main方法执行结果
// WAITING
// RUNNABLE

在子线程中去打印主线程和子线程各自的状态,明显调用了join方法的主线程被阻塞了是WAITING状态,而正在运行的子线程则是RUNNABLE状态。

join() 方法分析

join() 方法源码:

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

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");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

由此可知,join() 方法实际上还是调用了 wait() 方法的。如果没有传入时间参数,则是调用了 wait(0) 这个方法,代表永久等待,直到被唤醒。 有意思的是这其中并没有看到 notify() 或者是 notifyAll() 方法,也就是并没有线程去唤醒这个等待子线程执行完毕的主线程,但是当子线程执行完毕之后,这确确实实被唤醒了。

我们知道,主线程被唤醒的条件是子线程执行完毕,又知道线程执行完毕只有两种情况,一是 run() 方法运行结束,二是抛出了运行时异常。至此,答案水落石出,当线程执行完毕时,将会去执行 notifyAll() 方法唤醒其他的线程。

注:我们并不提倡使用 Thread 类的实例作为 synchronized 的锁对象原因也是在此,因为这可能会破坏原有的 wait-notify 结构。