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

相关推荐
wuxuanok几秒前
Web后端开发-分层解耦
java·笔记·后端·学习
kyle~32 分钟前
C/C++字面量
java·c语言·c++
纨妙37 分钟前
python打卡day59
开发语言·python
neoooo41 分钟前
别慌,Java只有值传递——一次搞懂“为啥我改了它还不变”!
java·后端·spring
秋难降41 分钟前
Python 知识 “八股”:给有 C 和 Java 基础的你😁😁😁
java·python·c
wuxuanok44 分钟前
Web后端开发-请求响应
java·开发语言·笔记·学习
livemetee1 小时前
spring-ai 1.0.0 (3)交互增强:Advisor 顾问模块
java
DDDDDouble1 小时前
<二>Sping-AI alibaba 入门-记忆聊天及持久化
java·人工智能
Sally璐璐1 小时前
IPSAN 共享存储详解:架构、优化与落地实践指南
开发语言·php
像风一样的男人@1 小时前
python --货车装厢问题
开发语言·python