【Java】Thread类中的join()方法原理

【Java】Thread类中的join()方法原理简介join()是Thread类的一个方法。根据jdk文档的定义:publicfinalvoidjoin()throwsInterruptedException:Waitsforthisthreadtodie.join()方法的作用,是等待这个线程结束;但显然,这样的定义并不清晰。个人认为”Java7ConcurrencyCookbook”的定义较为…

大家好,又见面了,我是你们的朋友全栈君。

简介

join()是Thread类的一个方法。根据jdk文档的定义:

public final void join()throws InterruptedException: Waits for this thread to die.

join()方法的作用,是等待这个线程结束;但显然,这样的定义并不清晰。个人认为”Java 7 Concurrency Cookbook”的定义较为清晰:

join() method suspends the execution of the calling thread until the object called finishes its execution.

也就是说,t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程。我们来看看下面的例子。

例子

我们对比一下下面这两个例子,看看使用join()方法的作用是什么?

  1. 不使用join()方法的情况:
public static void main(String[] args){
    System.out.println("MainThread run start.");

    //启动一个子线程
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("threadA run start.");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("threadA run finished.");
        }
    });
    threadA.start();

    System.out.println("MainThread join before");
    System.out.println("MainThread run finished.");
}

运行结果如下:

MainThread run start.
threadA run start.
MainThread join before
MainThread run finished.
threadA run finished.

因为上述子线程执行时间相对较长,所以是在主线程执行完毕之后才结束。

  1. 使用了join()方法的情况:
public static void main(String[] args){
    System.out.println("MainThread run start.");

    //启动一个子线程
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("threadA run start.");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("threadA run finished.");
        }
    });
    threadA.start();

    System.out.println("MainThread join before");
    try {
        threadA.join();    //调用join()
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("MainThread run finished.");
}

运行结果如下:

MainThread run start.
threadA run start.
MainThread join before
threadA run finished.
MainThread run finished.

对子线程threadA使用了join()方法之后,我们发现主线程会等待子线程执行完成之后才往后执行。

join()的原理和作用

java层次的状态转换图

【Java】Thread类中的join()方法原理

我们来深入源码了解一下join():

//Thread类中
public final void join() throws InterruptedException {
    join(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) {    //这个分支是无限期等待直到b线程结束
        while (isAlive()) {
            wait(0);
        }
    } else {    //这个分支是等待固定时间,如果b没结束,那么就不等待了。
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

我们重点关注一下这两句,无限期等待的情况::

while (isAlive()) {
    wait(0);    //wait操作,那必然有synchronized与之对应
}

注意这个wait()方法是Object类中的方法,再来看sychronized的是谁:

public final synchronized void join(long millis) throws InterruptedException { ... }

成员方法加了synchronized说明是synchronized(this),this是谁啊?this就是threadA子线程对象本身。也就是说,主线程持有了threadA这个对象的锁。

大家都知道,有了wait(),必然有notify(),什么时候才会notify呢?在jvm源码里:

// 位于/hotspot/src/share/vm/runtime/thread.cpp中
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
    // ...

    // Notify waiters on thread object. This has to be done after exit() is called
    // on the thread (if the thread is the last thread in a daemon ThreadGroup the
    // group should have the destroyed bit set before waiters are notified).
    // 有一个贼不起眼的一行代码,就是这行
    ensure_join(this);

    // ...
}


static void ensure_join(JavaThread* thread) {
    // We do not need to grap the Threads_lock, since we are operating on ourself.
    Handle threadObj(thread, thread->threadObj());
    assert(threadObj.not_null(), "java thread object must exist");
    ObjectLocker lock(threadObj, thread);
    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
    // Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
    java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
    // Clear the native thread instance - this makes isAlive return false and allows the join()
    // to complete once we've done the notify_all below
    java_lang_Thread::set_thread(threadObj(), NULL);

    // 同志们看到了没,别的不用看,就看这一句
    // thread就是当前线程,是啥?就是刚才例子中说的threadA线程啊。
    lock.notify_all(thread);

    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
}

当子线程threadA执行完毕的时候,jvm会自动唤醒阻塞在threadA对象上的线程,在我们的例子中也就是主线程。至此,threadA线程对象被notifyall了,那么主线程也就能继续跑下去了。

可以看出,join()方法实现是通过wait()(小提示:Object 提供的方法)。 当main线程调用threadA.join时候,main线程会获得线程对象threadA的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 (也就是子线程threadA执行完毕退出的时候)

总结

首先join() 是一个synchronized方法, 里面调用了wait(),这个过程的目的是让持有这个同步锁的线程进入等待,那么谁持有了这个同步锁呢?答案是主线程,因为主线程调用了threadA.join()方法,相当于在threadA.join()代码这块写了一个同步代码块,谁去执行了这段代码呢,是主线程,所以主线程被wait()了。然后在子线程threadA执行完毕之后,JVM会调用lock.notify_all(thread);唤醒持有threadA这个对象锁的线程,也就是主线程,会继续执行。

参考资料

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/140802.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 简单了解Activity工作流引擎

    简单了解Activity工作流引擎一、什么是工作流以请假为例,现在大多数公司的请假流程是这样的员工打电话(或网聊)向上级提出请假申请——上级口头同意——上级将请假记录下来——月底将请假记录上交公司——公司将请假录入电脑采用工作流技术的公司的请假流程是这样的员工使用账户登录系统——点击请假——上级登录系统点击允许就这样,一个请假流程就结束了有人会问,那上级不用向公司提交请假记录?公司不用将记录录入电脑?答案…

  • C语言自定义函数的方法

    C语言自定义函数的方法一、C语言之自定义函数的调用1.声明一个自定义函数:voidfun(void);//函数的声明也可在主函数之前编写自定义函数;2.主函数里调用自定义函数:intmain(void){fun();//调用fun函数;return0;}3.编写自定义函数的功能:voidfun(void){​ inta=12;​ printf(“a=%d”,a);输出a}源代码…

  • lcd像素点密度_常见液晶显示分辨率对应像素密度[通俗易懂]

    lcd像素点密度_常见液晶显示分辨率对应像素密度[通俗易懂]液晶屏尺寸主流屏幕分辨率屏幕像素密度(PPI)产品类型800×4803英寸:3113.5英寸:2664英寸:233960×6403.5英寸:3294英寸:2884.3英寸:2681280×7204.3英寸:3414.7英寸:3125英寸:2931920×10805英寸:4407英寸:3142048x1080_2K6英寸:3857英寸:3301280×8007英寸:2158英寸…

  • navacat15 激活码【在线注册码/序列号/破解码】

    navacat15 激活码【在线注册码/序列号/破解码】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • pycharm2021.12.13 10月29 激活码【2021.10最新】

    (pycharm2021.12.13 10月29 激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html1435QFILVV-eyJsaWN…

  • 快速排序算法详细图解JAVA_实现快速排序

    快速排序算法详细图解JAVA_实现快速排序高快省的排序算法有没有既不浪费空间又可以快一点的排序算法呢?那就是“快速排序”啦!光听这个名字是不是就觉得很高端呢。假设我们现在对“61279345108”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(不要被这个名词吓到了,就是一个用来参照的数,待会你就知道它用来做啥…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号