真正理解 Java的Thread#join

导读:本篇文章讲解 真正理解 Java的Thread#join,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

前言

Thread#join() 内部调用了 同步方法 Thread#join(long millis),该方法 由 synchronized 修饰,该方法内部 又调用了 Object#wait(0)

注:Object#wait(0) 和 Object#wait() 一样,都是让调用此方法的线程进入永久阻塞,唯一的区别就是,Object#wait(0) 让线程 变为 TIMED_WAITING 状态,而,Object#wait() 则是让线程变为 WAITING 状态。

Thread#join(long millis) 的 Javadoc

原文:

/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*/

大致意思是:最多等待当前线程 millis 毫秒,除非当前线程终止了。如果参数 millis=0 毫秒,那么,就意味着当前线程会进入永久阻塞。

示例:

public class MainThread {
    public static void main(String[] args) {
        System.out.println("mainThread " + Thread.currentThread().getState().name());

        Thread childThread = new Thread(() -> {
            try {
                System.out.println("childThread " + Thread.currentThread().getState().name());
                // childThread 休眠 3s
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "childThread");

        // 启动子线程
        childThread.start();

        try {
            // childThread 加入 mainThread
            // 其实就是 mainThread 会获得 childThread 实例作为锁,因为 childThread.join(0) 是 synchronized 修饰的
            // mainThread 调用了 childThread.wait(0) 进入永久阻塞
            // 除非 childThread 结束(TERMINATED),释放所有资源,否则 mainThread 会一直 处于 TIMED_WAITING 状态
            // 线程结束后,childThread 会调用 notifyAll, 唤醒所有的线程
            childThread.join();
            System.out.println("childThread " + childThread.getState().name());
            System.out.println("mainThread " + Thread.currentThread().getState().name());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面代码展示了两个线程:mainThread(主线程),childThread(子线程)。
通过 Thread childThread = new Thread(..); 新建childThread线程(此时 childThread 线程处于NEW状态);

然后调用childThread.start()(childThread线程状态转换为RUNNABLE);

再调用childThread.join(),此时,mainThread 线程会阻塞,一直等待childThread 线程运行完再继续运行。

所以,Thread#join 方法的大致作用为:线程B 使用 线程A 的 join方法,这个方法会阻塞线程B,直到线程A结束执行,线程B才会继续执行。

为此提出一个疑问?

  • 线程B 使用 childThread.join()时,到底发生了啥?

Thread#join() 源码分析

Thread#join 一共有三个重载版本,分别是无参、一个参数、两个参数:

其中,

  1. 三个方法都被final修饰,无法被子类重写;
  2. 有参的 join方法 都有 synchronized 关键字修饰,锁就是当前线程实例
    而执行 join方法 的另一个线程就会获得锁;
  3. 无参版本 和 两个参数版本,最终都调用了仅有一个参数的版本;
  4. join() 和 join(0) 效果相同的,都表示永久阻塞;join(非0)表示等待一段时间;

从源码可以看到, join(0) 调用了 Object.wait(0),其中Object.wait(0) 会永久阻塞,直到被 notify/notifyAll 唤醒为止。

  1. join() 和 sleep() 一样,可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了 wait(),会释放锁,而 sleep() 会一直保持锁。
public final void join() throws InterruptedException;

public final synchronized void join(long millis)throws InterruptedException;

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

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

    // 如果是 join(0),其实无参的 join方法就调用了 join(0)
    if (millis == 0) {
        // 循环,唤醒后再次判断,防止 虚假唤醒
        while (isAlive()) {
            // 调用 Object#wait(0), 永久阻塞
            // 除非,锁调用 notifyAll 或者 notify
            // 注:子线程(锁)结束后,就会调用 notifyAll
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            // 如果阻塞的时间 >= millis,直接结束
            if (delay <= 0) {
                break;
            }
            // 阻塞 millis 时间
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

分析

结合本文开头的实例 和 Thread#join() 源码,可以看到:

调用链:MainThread.main() -> childThread.join() -> childThread.join(0) -> childThread.wait(0)。

  1. MainThread 线程会获得 childThread 线程实例作为锁,其他线程可以进入 childThread.join(),但不可以进入 childThread.join(0), 因为childThread.join(0)是同步方法;

  2. 如果 childThread 线程是 Active,则调用 childThread.wait(0),为了防止childThread 被虚假唤醒, 需要将 wait(0) 放入 while(isAlive()) 循环中;

  3. 一旦 childThread 线程不为 Active (状态为 TERMINATED), 此时,childThread 线程就已经结束了,结束的线程最后都会调用 notifyAll 方法释放所有等待此线程锁的其他线程,MainThread 就是其中之一,所以,MainThread 被唤醒了,继续执行。

结论

  1. 子线程结束后,子线程的this.notifyAll()会被调用,join()返回,父线程只要获取到锁和CPU,就可以继续运行下去了。
  2. 调用 另一个线程的join()方法 的线程进入 TIMED_WAITING 状态,等待 join() 所属线程运行结束后再继续运行。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/69724.html

(0)

相关推荐

半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!