• 线程方法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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

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

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

  • 相关阅读:
    @staticmethod
    会议项目之审批
    腾讯云~kafka伪集群搭建
    手把手教你:轻松打造沉浸感十足的动态漫反射全局光照
    Grōk :马斯克 xAI 打造的 ChatGPT 竞争产品探索
    Linux 8:线程
    GPS+北斗定位借助Arduino的数值显示
    深度解析:什么是太爱速M抢单模式?
    动态规划学习4:5 最长回文子串 三种方法
    TypeScript系列之类型 string
  • 原文地址:https://blog.csdn.net/weixin_44525960/article/details/125610596