Java里面Join(),为什么等待的是主线程,而不是当前子线程?

498 阅读3分钟

1.问题描述

​ 当我们想要一个线程插队执行的时候,我们可能会使用到thread.join();。这个会让子线程先于主线程执行完毕,然后才开始执行子线程。但是仔细一想,发现这个明明调用的是子线程的join()方法,按道理应该子线程等待执行才是,为什么反而是主线程等待了呢?相关的示例代码如下:

public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程开始执行...");

        });

        thread.start();
        thread.join();
        System.out.println("主线程执行...");

    }

输出:

子线程开始执行...
主线程执行...

2.查看源码说明

带着这个主线程等待执行的疑惑来一起看下join的源码,如下所示:

/**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final void join() throws InterruptedException {
        join(0);
    }

谁等待谁终止?源码中注释说明的是等待这个线程终止,那就是等待调用Join()的线程终止,再继续往下看:

 /**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    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的源码中,我们可以看到它使用了while (isAlive()) 循环判断线程的存活状态,满足就调用wait方法,当有设置时长的时候会根据时长来进行等待。注意按照程序的执行顺序,我们这里是主线程调用的Thread的Join方法,所以是判断子线程的存活状态,满足则让子线程执行,主线程来等待。

wait 等待方法是让线程进入等待队列,使用方法是 obj.wait(); 这样当前线程就会暂停运行,并且进入obj的等待队列中,称作“线程正在obj上等待”。可以把子线程t理解为一个普通的obj对象,调用t的wait()方法,实际上就是主线程(main线程)在childThread对象的队列上等待,可以转换为如下写法进一步理解:

/**
 * 主线程
 */
public class MainTest {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始执行...");

        Thread childThread = new ChildThread();
        childThread.start();
        while (childThread.isAlive()) {
            synchronized (childThread) {
                // 此时主线程进行等待并释放锁资源 使子线程有得以运行的机会
                childThread.wait();
            }
        }

        System.out.println("主线程执行结束...");
    }
}

/**
 * 子线程
 */
class ChildThread extends Thread {
    public ChildThread() {
    }

    @Override
    public void run() {
        System.out.println("子线程开始执行...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程执行结束...");
        synchronized (this) {
            // 唤醒等待的主线程
            this.notifyAll();
        }
    }


}

输出:

主线程开始执行...
主线程执行结束...
子线程开始执行...
子线程执行结束...

另外需要强调的一点:使用Join方法让主线程等待后,调用完wait方法后,JVM底层会隐式的调用notifyAll方法来唤醒主线程,使其得以继续往下执行。