文档章节

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

shzwork
 shzwork
发布于 03/04 21:04
字数 1461
阅读 29
收藏 2

简介
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()方法的作用是什么?

不使用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.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行结果如下:

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

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

使用了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.");
}
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
运行结果如下:

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

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

join()的原理和作用
java层次的状态转换图

我们来深入源码了解一下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;
        }
    }
}
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
我们重点关注一下这两句,无限期等待的情况::

while (isAlive()) {
    wait(0);    //wait操作,那必然有synchronized与之对应
}
1
2
3
注意这个wait()方法是Object类中的方法,再来看sychronized的是谁:

public final synchronized void join(long millis) throws InterruptedException { ... }
1
成员方法加了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();
}
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
当子线程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这个对象锁的线程,也就是主线程,会继续执行。

参考资料
Java中Thread类的join方法到底是如何实现等待的?
简谈Java的join()方法
--------------------- 
作者:Mlib 
来源:CSDN 
原文:https://blog.csdn.net/u010983881/article/details/80257703 
版权声明:本文为博主原创文章,转载请附上博文链接!

本文转载自:https://blog.csdn.net/u010983881/article/details/80257703

shzwork
粉丝 15
博文 817
码字总数 10675
作品 0
厦门
私信 提问
Android线程管理(三)——Thread类的内部原理、休眠及唤醒

上文对ActivityThread的工作流程进行了分析,本文将对Thread类的内部原理以及休眠唤醒进行说明。 三、Thread类的内部原理、休眠及唤醒 3.1 Thread类的内部原理 线程是CPU资源调度的基本单位,...

yhthu
2017/05/22
0
0
iOS封装C语言P Thread

需求:iOS封装C语言P Thread以实现开始,结束,暂停,继续,指定线程名称,任务等的需求。 阅读前提: 了解p thread基本用法 了解iOS端线程基本概念 了解线程加锁基本原理 GitHub地址(附代码)...

__小___东邪___
2018/12/04
0
0
再深一点,理解线程的join方法

配图:曲径通幽 讲真,如果不是被面试官吊打,join()方法也还不会引起我的重视。因为,工作中确实没有使用过它。 现在,对它来个刨根问底。 join()方法的作用 在写这篇文章之前,我对join的理...

忙碌的键盘
2016/10/24
130
0
Java Thread.join()方法

join方法只有在继承了Thread类的线程中才有。 线程必须要start() 后再join才能起作用。 1. 使用方式 join是Thread类的一个方法,启动线程后直接调用。 2. 为什么使用join方法 在很多情况下,...

牧师-Panda
2016/09/17
22
0
黑马程序员——多线程篇

------- android培训、java培训、期待与您交流! ---------- 进程:正在执行的程序。 线程:是进程中用于控制程序执行的控制单元(执行路径,执行情景),进程中至少有一个线程。 对于JVM,启动...

optimisticpig
2014/04/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

高速PCB设计软件allegro中与网络有关的约束规则设置

在allegro pcb的设计过程中,设计约束规则包括时序规则、间距规则、信号完整性规则以及物理规则等,本期主要详细讲解与物理、间距与电气约束中的线宽、线间距物理规则的设置。 一、线宽设置 ...

demyar
20分钟前
2
0
Linux 启动停止SpringBoot jar 程序部署Shell 脚本

#!/bin/bash #这里可替换为你自己的执行程序,其他代码无需更改 APP_NAME=algorithm.jar #使用说明,用来提示输入参数 usage() { echo "Usage: sh 执行脚本.sh [start|stop|restart|status]...

草庐过客
22分钟前
3
0
mysql-connector-java驱动升级到8.0后数据库保存时间出现时差

1.问题:在一个新项目中用到了新版的mysql jdbc 驱动后,发现保存到数据库的时间出现了时差 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId>......

ValSong
24分钟前
3
0
好程序员大数据教程Scala系列之隐式转换和隐式参数

5.1. 概念 隐式转换和隐式参数是Scala中两个非常强大的功能,利用隐式转换和隐式参数,你可以提供优雅的类库,对类库的使用者隐匿掉那些枯燥乏味的细节。 5.2. 作用 隐式的对类的方法进行增强...

好程序员官网
28分钟前
3
0
多线程必备

初次接触线程,可能有很多初学者搞不明白,始终云里雾里,那么本篇文章直接带大家介绍多线程必须知道的几个点 接下来没有多余,直接上干货 1. 进程和线程的区别是什么? 进程是执行着的应用程序,...

理性思考
31分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部