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)上进入等待状态

相关推荐
The Future is mine38 分钟前
Python计算经纬度两点之间距离
开发语言·python
Enti7c39 分钟前
HTML5和CSS3的一些特性
开发语言·css3
腥臭腐朽的日子熠熠生辉1 小时前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
爱吃巧克力的程序媛1 小时前
在 Qt 创建项目时,Qt Quick Application (Compat) 和 Qt Quick Application
开发语言·qt
ejinxian1 小时前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
独好紫罗兰1 小时前
洛谷题单3-P5719 【深基4.例3】分类平均-python-流程图重构
开发语言·python·算法
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿