下面是一段司空见惯的代码,创建两个线程A和线程B,使得线程A优先于线程B执行,使得线程B优先于主线程执行

public class Demo52 {
public static void main(String[] args) { Thread thread1 = new Thread(()->{
System.out.println("线程:"+Thread.currentThread().getName());
},"A");
Thread thread2 = new Thread(()->{
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName());
},"B");
thread1.start();
thread2.start();
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程");
}
}

输出结果:

线程:A
线程:B
main线程

它是如何做到的线程A优先于线程B,线程B优先于主线程的呢?

为了说明这点,就要查看Thread.join的源码了:

     /** 等待该线程终止
* Waits for this thread to die.
* 调用此方法的行为方式与调用完全相同join (0)
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
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) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
public final native void wait(long timeout) throws InterruptedException;

​ 结合上面的实例,先看主线程和线程B,在主线程中执行“thread2.join();”,也就相当进入到join方法体中,它会获取到当前实例的锁,也就是线程B对象的锁,然后判断线程是否存活后执行“ wait(0);”,执行该方法时,主线程会释放锁,进入到阻塞状态(也即进入到了该锁的WaitSet中)。为什么说是主线程进入到阻塞状态,而不是线程B进入到阻塞状态呢?

为了解答这个问题,设计如下的实例:

class ThreadTest extends Thread{
public synchronized void method1(){
System.out.println("hello world");
System.out.println(Thread.currentThread().getName());
}
}
public class Demo51 {
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
threadTest.method1();
}
}

输出结果:

hello world
main

​ 可以看到在执行“threadTest.method1()”时,线程“threadTest”中输出的当前线程是主线程,而不是“Thread-0”,这是因为它是“ threadTest.method1()”是由主线程所调用的。

​ 再回到上面的问题中,调用“thread2.join();”导致主线程被阻塞,紧接着线程B开始执行,线程B执行完毕后开始执行主线程,而此时主线程还在阻塞状态(即还在该线程锁的waitset中),那么它是如何实现唤醒主线程,使得它能够接着执行的呢?实际上这是因为每个线程在退出时,会执行notifyAll唤醒所有阻塞在该实例锁上的线程。

为了更为方便的说明这个问题,设计如下的实例,创建一个线程类,定义“method1”方法和“run”方法,然后在主线程中调用它

class ThreadTest extends Thread{

    public synchronized void method1() throws InterruptedException {
System.out.println("hello world");
System.out.println(Thread.currentThread().getName());
wait();
System.out.println("==================");
} @Override
public void run() {
try {
Thread.sleep(10_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Demo51 {
public static void main(String[] args) throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
threadTest.method1();
System.out.println("===Exit===");
}
}

运行结果:

hello world
main
==================
===Exit===

​ 来分析一下程序的执行,“ThreadTest threadTest = new ThreadTest()”创建线程对象,“threadTest.start();”启动这个线程,“threadTest.method1();”执行method1方法,关注点就在这里,在执行method1方法的时候,主线程会尝试获取“threadTest ”对象的锁,成功后进入到方法体,然后进入到阻塞状态,而threadTest 在启动后执行run方法中的内容,然后睡眠10秒钟,在随眠结束后执行完毕,threadTest 退出执行状态,在退出时,执行notify_all方法唤醒阻塞在threadTest 锁上的主线程。

// 位于/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();
}

————————————————

版权声明:本文为CSDN博主「Mlib」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/u010983881/article/details/80257703

​ 同理在Thread.join中,也是如此。如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回;它的底层实现就是线程A进入到了thread对象的waitset中了,当thread的线程执行完毕后,在线程退出操作中,会自动唤醒阻塞在thread对象上的线程A,这样线程A也就能够继续执行了,表现为线程A等待thread执行完毕后,才接着执行。

参考链接:Java Thread的join() 原理

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

(四)Thread.join的作用和原理

由Thread.join引发的思考的更多相关文章

  1. C#关于在返回值为Task方法中使用Thread.Sleep引发的思考

    起因 最近有个小伙伴提出了一个问题,就是在使用.net core的BackgroundService的时候,对应的ExecuteAsync方法里面写如下代码,会使程序一直卡在当前方法,不会继续执行,代 ...

  2. Thread.Sleep引发ThreadAbortException异常

    短信平台记录日志模块,是通过异步方式来记录的,即日志工具类里初始化一个Queue对象,公共的写日志方法的处理逻辑是把日志消息放到Queue里.构造器里设定一个死循环,不停的读队,然后把日志消息持久化到 ...

  3. Spring之LoadTimeWeaver——一个需求引发的思考---转

    原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver——一个需求引 ...

  4. 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考

    2018年12月12日18:44:53 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考 案件现场 不久前,在开发改造公司一个端到端监控日志系统的时候,出现了一 ...

  5. C# Thread.Join();Thread.Abort();

    Join() 等待当前线程运行完成后,才继续执行主线程后续代码: Abort() 结束当前线程,继续执行主线程后续代码: Thread.Join(); static void Main(string[ ...

  6. thread.join 从异步执行变成同步

    Java的线程模型为我们提供了更好的解决方案,这就是join方法.在前面已经讨论过,join的功能就是使用线程 从异步执行变成同步执行 当线程变成同步执行后,就和从普通的方法中得到返回数据没有什么区别 ...

  7. Thread.join()方法

    thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程.比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B.t.join() ...

  8. 由一篇文章引发的思考——多线程处理大数组

    今天领导给我们发了一篇文章文章,让我们学习一下. 文章链接:TAM - Threaded Array Manipulator 这是codeproject上的一篇文章,花了一番时间阅读了一下.文章主要是 ...

  9. 由SecureCRT引发的思考和学习

    由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...

随机推荐

  1. 面试题五十七:和为s的数字

    题目一:和为s的数字,在一个递增数组中寻找两个数字的和等于s 方法:双指针法,一个在头一个在尾:如果两个指针指向的和小于,那么be++:大于end--: 题目二:打印所有和为s的连续正数序列 方法:双 ...

  2. IO—》Properties类&序列化流与反序列化流

    Properties类 介绍:Properties 类表示了一个持久的属性集.Properties 可保存在流中或从流中加载.属性列表中每个键及其对应值都是一个字符串. Properties类特点: ...

  3. MacOS下JDK8的安装与配置

    微信搜索"艺术行者",关注并回复关键词"jdk8"获取安装包和API文档资料! 一.安装环节 1.打开网页 https://www.oracle.com/jav ...

  4. 巩固复习(Django最基础的部分_具体查看官方文档)

    Django学习路1 1.脚本不能随便运行,没准 linux 运行完就上不去了 2.pip 在 linux 上 写 pip3 同理 python 写为 python3 3.在 pycharm 上安装库 ...

  5. Python环境搭建及中文编码

    Python 官方下载地址:https://www.python.org/downloads/ 64 位下载客户端  32 位下载客户端 在安装之后,可能在桌面上看不到图标.这时可以到开始菜单下寻找 ...

  6. Python按值传递参数和按引用传递参数

    Python按值传递参数和按引用传递参数: 按值传递参数: 使用一个变量的值(数字,字符串),放到实参的位置上 注:传递过去的是变量的副本,无论副本在函数中怎么变,变量的值都不变 传递常量: # 传递 ...

  7. Python日期和时间_什么是Tick_什么是时间元组_获取当前时间

    Python 日期和时间_什么是 Tick _什么是时间元组: 时间和日期:某年某月某日某时某分某秒 Tick: 时间间隔以 秒 为单位的浮点小数,起始时间为:1970年1月1日0点0分开始 # Ti ...

  8. Python os.rmdir() 方法

    概述 os.rmdir() 方法用于删除指定路径的目录.仅当这文件夹是空的才可以, 否则, 抛出OSError.高佣联盟 www.cgewang.com 语法 rmdir()方法语法格式如下: os. ...

  9. PHP parse_ini_file() 函数

    定义和用法 parse_ini_file() 函数解析一个配置文件(ini 文件),并以数组的形式返回其中的设置. 语法 parse_ini_file(file,process_sections) 参 ...

  10. CF R630 div2 1332 E Height All the Same

    LINK:Height All the Same 比赛的时候 被这道题给打自闭了 还有1个多小时的时候开始想 想了30min 无果 放弃治疗. 心态炸了 F不想看了 应该要把题目全看一遍的 下次不能这 ...