线程方法join/join(timeout)源码分析


join/join(timeout)方法可以通知让主线程等待,直到子线程结束后,主线程再继续执行,那么究竟是怎样实现的呢?

本章内容主要探究的问题:

(1). 从源码层面剖析join/join(timeout)方法到底做了什么?

(2). 线程join方法阻塞,那么什么时候唤醒?如何唤醒?

好了接下来一起分析…

  1. 在java中的join方法是调用重载的join(timeout)方法(图1-1),相当于join(0);在join(timeout)中,是一个synchronized修饰的方法,里面会判断传入的时间参数不能小于0,小于抛出参数异常;
  2. 接下来如果传入的参数为0,也就是join()方法调用进来的,会调用isAlive()方法检查线程是否存活,存活则调用wait(0)方法让其进入无限等待…
  3. 如果参数大于0,也就是join(timeout)调用进来的,会根据传入的时间参数和当前时间进行计算,时间已到则退出,否则继续wait(timeout)循环等待。
    注意:
    这里的isAlive()方法是判断调用此join()方法的线程是否存活(t1线程),这里的wait()/wait(timeout)方法是让主线程进行等待(mian线程)。

synchronized锁是this锁,也就是说谁来调用join()方法,谁就是锁(这里锁就是t1)。
​​​​在这里插入图片描述
(图1-1)
4. 好了,到了这里,也就是说join()方法的本质是wait()方法;当调用wait(timeout)方法,timeout>0则到了等待时间后自动唤醒,timeout=0时,会无限等待,那么无线等待由谁来唤醒呢?wait方法可以看我之前的博客线程方法wait/wait(timeout)源码分析)

  1. 若是对多线程有多一些研究的朋友就就会了解到,当线程执行结束后(也就是出了}花括号之后),会在jvm层面调用exit()方法进行释放锁。
    jvmJavaThread::exit()方法中,会调用一个ensure_join()方法(图1-2),在此方法中,创建了ObjectLocker对象,实际上是进行了加锁(构造函数加锁);调用lock.notify_all()方法进行挪动在waitSet的线程节点;还并没有唤醒,也就是说…现在已经加了锁,在哪里释放锁呢?

注意:(线程唤醒并不是notify方法唤醒的,notify方法只是挪动线程包装节点(ObjectWaiter),真正唤醒是在出了锁之后唤醒,可以阅读我之前的文章线程方法notify/notifyAll源码分析)。

ensure_join方法逻辑(图1-2)

=========================================================
c++语法中,有一种析构函数,析构函数与构造函数是相反的,构造方法用于开辟内存空间,创建对象;而析构函数用于资源释放;析构函数是c++自动调用的;也就是说,调用构造函数创建对象,使用完后会自动调用析构函数释放资源。
构造函数为ObjectLocker(),析构函数为~ObjectLocker()。(图1-3)

  1. 释放锁:这里是通过构造创建了ObjectLocker对象,实现了加锁,在方法结束时,会自动调用~ObjectLocker析构函数,在析构函数中进行释放锁,唤醒线程。

在这里插入图片描述
(图1-3)

也就是说,在线程执行结束后,在jvmc++层面为你(加锁(线程的内置锁),唤醒,释放锁)。

=========================================================
代码证明:
测试:有t1和t2两个线程,t1进入后打印 t1…start,然后休眠2s;t2线程持有t1的锁,也会打印 t2…start,休眠5s后挂起;

按照逻辑,t1只休眠2s,会先醒来,打印 t1…end 然后t1执行结束;

t2在5s后醒来,打印t1线程是否存活,发现t1线程仍然存活!然后wait挂起,最后居然打印出了 t2…end。

疑问:当t1执行完后,t2打印t1的线程状态,为何还是处于存活状态? t2线程中调用了wait方法等待,是如何被唤醒的呢?有兴趣可以把下面代码运行试一试

/**
 * 测试线程join方法
 *
 * 测试:有t1和t2两个线程,t1进入后打印 t1...start,然后休眠2s;t2线程持有t1的锁,也会打印 t2...start,休眠5s后挂起;
 * 按照逻辑,t1只休眠2s,会先醒来,打印 t1..end 然后t1执行结束;
 * t2在5s后醒来,打印t1线程是否存活,发现t1线程仍然存活!然后wait挂起,最后居然打印出了 t2...end。
 * 疑问:1.当t1执行完后,t2打印t1的线程状态,为何还是处于存活状态?
 *      2.t2线程中调用了wait方法等待,是如何被唤醒的呢?
 *
 * join方法的本质,是判断join的线程是否存活,如果存活,那么当前线程将wait等待直至join的线程死亡; 实质上是调用wait方法
 * 何时唤醒?当join的线程执行结束后,会在jvm的c++层面(加锁(t1的内置锁),唤醒,释放锁),其中调用了notifyAll方法
 * 也就是在锁退出时,调用c++的exit方法,在exit中的ensure_join方法里,创建ObjectLocker对象进行加锁,然后~ObjectLocker析构函数里释放锁
 *
 * 解答:1. t1线程执行完后,t2线程休眠时,t1其实并没有真正执行结束,因为还需要进行(加锁,唤醒,释放锁),而现在t2线程拿着t1的锁,
 *         t1只有等待t2执行完后才能获取到锁,所以在t2的5s醒来之后,打印t1仍为存活状态。
 *      2.在t1执行完之后,在jvm的c++层面进行(加锁,唤醒,释放锁),所以t2是在这里进行唤醒的。
 */
public class TestJoin {

    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "..start");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "..end");
            }//线程执行结束后 会在jvm的c++层面会给你(加锁(t1的内置锁),唤醒,释放锁)
        }, "t1");


        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (t1) {//使用t1锁时,当t1线程执行完毕后会被唤醒
                    System.out.println(Thread.currentThread().getName() + "..start");
                    try {
                        Thread.sleep(5000);
                        System.out.println(t1.isAlive());//睡眠5s,占用着t1的锁,所以t1无法真正执行完毕(因为线程结束后需要加锁,t1的内置锁),t1线程仍然存活
                        t1.wait();//执行wait方法后释放t1的锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "..end");
                }
            }
        }, "t2");
        
        t1.start();
        t2.start();
    }
}

总结:join/join(timeout)方法本质上是调用的wait/wait(timeout)方法。当线程结束后,会在jvm的c++层面给你(加锁,唤醒,释放锁)。

感谢各位的阅读,如有不正确的地方,欢迎评论指正!

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 30
    评论
### 回答1: 这是 Python 中 threading 模块中的方法,用于阻塞当前线程,直到被调用 join 方法线程结束或超时。如果不传入 timeout 参数,则会一直阻塞直到被调用 join 方法线程结束。如果传入 timeout 参数,则会阻塞指定时间后,如果被调用 join 方法线程还没有结束,当前线程会继续执行。如果被调用 join 方法线程已经结束,则 join 方法会立即返回。 ### 回答2: `def join(self, timeout=None):` 是Python中`threading.Thread`类的一个方法,用于等待线程执行完成。 参数`self`表示当前线程对象。`timeout`是可选的等待时间,以秒为单位,如果指定了timeout且超时时还没有等到线程执行完成,则会抛出`threading.TimeoutError`异常。 在调用`join()`方法时,主线程会等待该线程执行完成。如果线程已经执行完成或者已经被终止,`join()`方法会立即返回。如果`timeout`值为None,则join()方法会一直等待,直到线程执行完成。如果`timeout`值为一个正数,则join()方法最多等待timeout秒,如果超过这个时间还没有执行完成,则会返回。 在多线程的情况下,调用join()方法可以确保主线程会等待所有的子线程执行完成后再继续执行后续的代码。这在需要等待多个线程完成某个任务后再进行下一步操作的情况下非常有用。 例如: ```python import threading def func(): print("子线程开始执行") # 模拟耗时操作 time.sleep(3) print("子线程执行完成") t1 = threading.Thread(target=func) t2 = threading.Thread(target=func) t1.start() t2.start() t1.join() t2.join() print("主线程执行完成") ``` 在上面的例子中,我们创建了两个子线程,每个子线程都会执行`func()`函数,`func()`函数模拟了一个耗时操作,执行时间为3秒。在主线程中,通过调用`join()`方法等待子线程执行完成后,才会输出"主线程执行完成"。这样能够确保主线程只有在所有子线程都执行完成后再继续执行后续的代码。 ### 回答3: join(self, timeout=None) 是Python中threading模块中的一个方法。它被用于等待一个线程直到它终止。 这个方法接受一个可选的timeout参数,用于指定最长等待终止的时间。当timeout参数被指定时,join()会阻塞调用线程,直到被调用的线程结束或超过了指定的timeout时间。 如果timeout参数没有指定或者为None时,join()会阻塞调用线程,直到被调用的线程结束。 当被调用的线程结束后,join()方法会返回None。 join()方法通常与start()方法配合使用,用于等待一个线程的结束。例如: ``` import threading def my_func(): # 执行线程的任务 my_thread = threading.Thread(target=my_func) my_thread.start() my_thread.join(timeout=5) # 等待线程执行完成或者超过5秒钟 ``` 上面的代码中,我们创建了一个线程my_thread,并调用start()方法启动线程。然后使用join()方法等待线程执行结束或者超过5秒钟。 值得注意的是, 如果一个线程已经终止,再次调用它的join()方法会立即返回。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值