你真得懂Thread.join吗?

Thread类中的join方法,用于等待某个线程执行结束。

简单示例

以下简单的代码,会让主线程等待子线程执行结束再执行。如果去掉t.join(),可能主线程就直接退出了,子线程都来不及执行。

package com.qcy.testJoin;

/**
 * @author qcy
 * @create 2020/09/10 17:17:03
 */
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t.start();
        t.join();
    }
}

join源码分析

t.join()源码如下:

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

接着是调用join的单参数重载方法,传入等待时间0,表示一直等待下去

这一点也可以从重载方法的注释中看到

你真得懂Thread.join吗?_第1张图片

单参数的重载方法如下:以主线程调用t.join()为例

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

(1)由于这里出现了synchronized,因此主线程需要拿到子线程对象t的锁。

(2)当millis为0的时候,循环判断isAlive(),即判断线程是否存活,如果存活,调用子线程对象的wait方法,传入0也是表示一直等待下去

    /**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     *
     * @return  true if this thread is alive;
     *          false otherwise.
     */
    public final native boolean isAlive();

(3)当主线程调用子线程对象的wait方法后,主线程释放掉锁,并进入等待状态。

(4)当子线程运行结束,不再是存活状态,那么主线程需要被唤醒,且需要再次获取到锁,才能继续运行join剩余的方法,运行结束后,主线程从join方法中返回,继续运行main剩余的方法。


JVM底层代码分析

问题来了,主线程什么时候被唤醒?唤醒代码又是在哪里的呢?

这要看jvm的底层代码,在thread.cpp代码中,地址thread.cpp地址

在线程退出的代码中,调用了ensure_join()方法

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {

  ....

  // Notify waiters on thread object. This has to be done after exit() is called
  // on the thread (if the thread is the last thread in a daemon ThreadGroup the
  // group should have the destroyed bit set before waiters are notified).
  ensure_join(this);

  ....

  // Remove from list of active threads list, and notify VM thread if we are the last non-daemon thread
  Threads::remove(this);
}

从ensure_join(this)上方的注释可以了解到,该方法是唤醒在该线程对象上的等待者。继续看他的源码

static void ensure_join(JavaThread* thread) {
  // We do not need to grap the Threads_lock, since we are operating on ourself.
  Handle threadObj(thread, thread->threadObj());
  assert(threadObj.not_null(), "java thread object must exist");
  ObjectLocker lock(threadObj, thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
  // Thread is exiting. So set thread_status field in  java.lang.Thread class to TERMINATED.
  java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
  // Clear the native thread instance - this makes isAlive return false and allows the join()
  // to complete once we've done the notify_all below
  java_lang_Thread::set_thread(threadObj(), NULL);
  lock.notify_all(thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
}

java_lang_Thread::set_thread(threadObj(), NULL)语句,由上面的注释,可以了解到该方法会清除本地线程实例,将会使得isAlive()返回false,接着在调用完lock.notify_all(thread)语句后,即通知在此线程对象上的等待者,我们例子中的主线程在重新获得CPU分配的时间片后,会直接从join方法中返回。


其他方式

至此,我们了解到了join方法的原理,不过join我们很少用到,在主线程等待子线程执行结束再执行的场景下,有更好更优雅的方法,可以参考我的另外一篇博客面试官:如何让主线程等待所有的子线程执行结束之后再执行?我懵了

你可能感兴趣的:(JAVA,#,多线程,join,Thread,wait)