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

相关推荐
普通的冒险者3 分钟前
几个简单的数组小练习(适合初学)
java·数据结构
keke103 分钟前
Java【10_1】用户注册登录(面向过程与面向对象)
java·python·intellij-idea
程序员buddha4 分钟前
Spring & Spring Boot 常用注解整理
java·spring boot·spring
Asus.Blogs19 分钟前
为什么go语言中返回的指针类型,不需要用*取值(解引用),就可以直接赋值呢?
开发语言·后端·golang
青瓦梦滋24 分钟前
【语法】C++的多态
开发语言·c++
C_V_Better29 分钟前
Java Spring Boot 控制器中处理用户数据详解
java·开发语言·spring boot·后端·spring
胡子洲34 分钟前
Spring Boot 应用中实现基本的 SSE 功能
java·spring boot·后端
t1987512836 分钟前
基于Qt的OSG三维建模
java·开发语言
SoFlu软件机器人1 小时前
Java 框架配置自动化:告别冗长的 XML 与 YAML 文件
xml·java·自动化
贰拾wan1 小时前
【Java-EE进阶】SpringBoot针对某个IP限流问题
java·spring boot·后端·idea