Thread.join源码分析

1. 源码

java 复制代码
	// 源码
    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()) {
            	// 参数为0表示无限期的等待
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

这是Thread.join的核心代码,实际上join方法有重载,分别为join()和join(long millis),在join()方法中实际上执行的是join(0);所以最终都会进入到上述代码块中。

我们知道当调用一个线程对象的join方法时,实际上是让当前线程等待该线程对象执行完成。这里要区分当前的执行线程线程对象,这是两个概念

2. 执行过程

先分析下参数millis为0的情况,当参数为0时,我们只需要关注

java 复制代码
        if (millis == 0) {
            while (isAlive()) {
            	// 参数为0表示无限期的等待
                wait(0);
            }
        }

这段代码即可,可以看到使用while循环判断isAlive()的结果,当结果为真时执行wait方法,首先说一下,isAlive是Thread的一个方法

java 复制代码
	// 测试此线程对象是否处于活动状态,如果此线程已经开始运行且尚未终止则属于活动状态
    public final native boolean isAlive();

注意这里的线程对象,指的可不是执行线程,是this对象,比如你调用thread.join(),那么isAlive指的就是thread对象,即使执行到该处的线程实际上可能是其他线程。

那么继续往下走,当判断线程处于活动状态时,触发wait()方法,该方法使当前执行线程在this对象上等待并释放this对象锁,那么到此则执行线程就进入等待了,根据join方法的注释得知,当this线程执行完毕时JVM会调用this线程对象上的notifyAll()方法唤醒该对象上的所有等待线程,所以执行线程此刻实际上是在等待this线程执行完成并通知它。

当this对象执行完成后执行线程会从等待集中被唤醒,然后尝试重新获取this锁,获取锁之后再次进行判断时发现isAlive()的结果已经不是true了,说明this线程执行完毕了,那么到此刻结束join方法。

3. 带时间参数的执行过程

java 复制代码
    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()) {
            	// 参数为0表示无限期的等待
                wait(0);
            }
        } else {
            while (isAlive()) {
            	// 假定第一次进入循环先将入参减去0直接获得需要等待的时长,实际上就是millis,后续再次进入now的值会发生变化
                long delay = millis - now;
                // 如果≤0则跳出,表示不需要等待了
                if (delay <= 0) {
                    break;
                }
                // 直接将执行线程等待指定时长 超时会被JVM自动唤醒
                wait(delay);
                // 计算已经等待了多久 now实际上就是计算执行线程等待的时长
                now = System.currentTimeMillis() - base;
            }
        }
    }

其实这里带的时间参数只是让执行线程等待指定时间,如果超时则执行线程也不再等待继续向下执行而已,在上述代码中可能有小伙伴疑惑为啥最后还要计算now的等待时长,wait(delay)方法不是指定了等待时间的吗,被唤醒那肯定是到时间了,now肯定等于delay啊,直接继续往下走就可以了。实际上可能存在欺骗性唤醒其他线程调用this上的notifyAll()方法导致执行线程提前被唤醒,这是不确定的,所以我们不能假定执行线程唤醒就是到了规定的等待时间,必须进行再次判断。其他过程和上述不带参的流程基本一致,不再赘述。

4. 示例代码

java 复制代码
public class JoinExample {

    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        Thread t1 = new Thread(() -> {
            Tools.randomPause(1000);
            System.out.println("我是线程t1");
        });
        t1.setName("t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            Tools.randomPause(1000);
            System.out.println("我是线程t2");
        });
        t2.setName("t2");
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

这里我修改了main线程的名称,并且新建了两个线程,现在启动这两个线程并且使main线程等待t1和t2执行完成,我在join方法入口、t1和t2执行处打断点,等下我们看下详细信息

进入join方法,我们可以看到当前执行线程是主线程,也就是main线程,this是t2,所以这里isAlive()验证的是t2是否处于活动状态,如果需要等待则main线程在t2实例(因为当前对象this是t2)上进入等待状态

相关推荐
blue_blooded19 分钟前
对College数据进行多模型预测(R语言)
开发语言·r语言
努力的小帅30 分钟前
C++_红黑树树
开发语言·数据结构·c++·学习·算法·红黑树
CN-Dust31 分钟前
【C++】指针
开发语言·c++
hweiyu0034 分钟前
学习Scala语言的最佳实践有哪些?
开发语言·学习·scala
许野平1 小时前
Rust:anyhow::Result 与其他 Result 类型转换
服务器·开发语言·rust·result·anyhow
Sammyyyyy1 小时前
Node.js 是怎么一步步撼动PHP地位的
开发语言·node.js·php
lly2024061 小时前
Node.js 路由
开发语言
程序员编程指南1 小时前
Qt 多线程调试技巧与常见问题
c语言·开发语言·c++·qt
程序媛一枚~2 小时前
使用Python,OpenCV计算跑图的图像彩色度
开发语言·python·opencv
golitter.2 小时前
python中的 @dataclass
开发语言·python