Thread.join源码分析

1. 源码

	// 源码
    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时,我们只需要关注

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

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

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

注意这里的线程对象,指的可不是执行线程,是this对象,比如你调用thread.join(),那么isAlive指的就是thread对象,即使执行到该处的线程实际上可能是其他线程。
那么继续往下走,当判断线程处于活动状态时,触发wait()方法,该方法使当前执行线程在this对象上等待并释放this对象锁,那么到此则执行线程就进入等待了,根据join方法的注释得知,当this线程执行完毕时JVM会调用this线程对象上的notifyAll()方法唤醒该对象上的所有等待线程,所以执行线程此刻实际上是在等待this线程执行完成并通知它。
join方法注释
当this对象执行完成后执行线程会从等待集中被唤醒,然后尝试重新获取this锁,获取锁之后再次进行判断时发现isAlive()的结果已经不是true了,说明this线程执行完毕了,那么到此刻结束join方法。

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

    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. 示例代码

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

`join()` 方法是 `Thread` 类中的一个方法,它的作用是让调用该方法的线程等待该线程执行完毕。当一个线程使用 `join()` 方法时,调用线程会被阻塞,直到被调用线程执行完毕后才会继续执行。 下面是 `join()` 方法的源码: ``` public final void join() throws InterruptedException { join(0); } ``` 可以看到,`join()` 方法实际上是调用了另一个重载方法 `join(long millis)`,并将参数设置为 0。 ``` 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()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } ``` 可以看到,`join(long millis)` 方法是一个同步方法,它首先对传入的参数进行检查,如果小于 0,则会抛出异常。 如果传入的参数为 0,则调用 `wait(0)` 方法,这会使当前线程进入等待状态,直到被调用线程执行完毕后才会被唤醒。 如果传入的参数不为 0,则会在循环中检查被调用线程是否还活着,如果还活着,则调用 `wait(delay)` 方法,这会使当前线程进入等待状态,等待一定的时间后被唤醒。在每次循环中,都会更新当前时间 `now`,并计算需要等待的时间 `delay`。 如果被调用线程已经执行完毕或者等待时间已经超过了传入的参数,则会跳出循环,方法执行完毕。 总的来说,`join()` 方法的本质是利用了 `wait()` 和 `notify()` 方法实现线程的同步和等待。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值