由Thread.join引发的思考
下面是一段司空见惯的代码,创建两个线程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执行完毕后,才接着执行。
由Thread.join引发的思考的更多相关文章
- C#关于在返回值为Task方法中使用Thread.Sleep引发的思考
起因 最近有个小伙伴提出了一个问题,就是在使用.net core的BackgroundService的时候,对应的ExecuteAsync方法里面写如下代码,会使程序一直卡在当前方法,不会继续执行,代 ...
- Thread.Sleep引发ThreadAbortException异常
短信平台记录日志模块,是通过异步方式来记录的,即日志工具类里初始化一个Queue对象,公共的写日志方法的处理逻辑是把日志消息放到Queue里.构造器里设定一个死循环,不停的读队,然后把日志消息持久化到 ...
- Spring之LoadTimeWeaver——一个需求引发的思考---转
原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver——一个需求引 ...
- 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考
2018年12月12日18:44:53 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考 案件现场 不久前,在开发改造公司一个端到端监控日志系统的时候,出现了一 ...
- C# Thread.Join();Thread.Abort();
Join() 等待当前线程运行完成后,才继续执行主线程后续代码: Abort() 结束当前线程,继续执行主线程后续代码: Thread.Join(); static void Main(string[ ...
- thread.join 从异步执行变成同步
Java的线程模型为我们提供了更好的解决方案,这就是join方法.在前面已经讨论过,join的功能就是使用线程 从异步执行变成同步执行 当线程变成同步执行后,就和从普通的方法中得到返回数据没有什么区别 ...
- Thread.join()方法
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程.比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B.t.join() ...
- 由一篇文章引发的思考——多线程处理大数组
今天领导给我们发了一篇文章文章,让我们学习一下. 文章链接:TAM - Threaded Array Manipulator 这是codeproject上的一篇文章,花了一番时间阅读了一下.文章主要是 ...
- 由SecureCRT引发的思考和学习
由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...
随机推荐
- ubuntu 12.04无盘工作站
注释:该篇博文是借鉴下列文章加上自己实践总结得来: a. http://forum.ubuntu.org.cn/viewtopic.php?f=77&t=117754 b. http://bl ...
- python爬虫实践——爬取“梨视频”
一.爬虫的基本过程: 1.发送请求(请求库:request,selenium) 2.获取响应数据()服务器返回 3.解析并提取数据(解析库:re,BeautifulSoup,Xpath) 4.保存数据 ...
- kylin streaming原理介绍与特点浅析
目录 前言 kylin streaming设计和原理 架构介绍 streaming coordinator streaming receiver cluster kylin streaming数据构建 ...
- JPA第一天
学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"springdata"获取视频和教程资料! b站在线视 ...
- PHP jdtounix() 函数
------------恢复内容开始------------ 实例 把格利高里历法的日期转换为儒略日计数,然后把儒略日计数转换为 Unix 时间戳: <?php$jd=gregoriantojd ...
- PHP timezone_name_get() 函数
------------恢复内容开始------------ 实例 返回时区的名称: <?php$tz=timezone_open("Europe/Paris");echo ...
- Prism.Interactivity 和 Prism.Modularity 介绍
Prism.Interactivity: 主要用来截取View即界面的一些处理,而这些功能通过vm 不好实现,只能用 CommandBehaviorBase 来截取处理,特别是在处理界面异常很有用. ...
- setOff与scrollTop区别
1.offsetTop : 当前对象到其上级层顶部的距离. 不能对其进行赋值.设置对象到页面顶部的距离请用style.top属性. 2.offsetLeft : 当前对象到其上级层左边的 ...
- Electron~增量更新
增量更新说明文档 English Version 提前准备 准备本地或者远程服务器或者远程静态文件url npm i -g http-server cd yourFileFolder // 进入任意文 ...
- 郭超:阿里云Cassandra背后的故事
大家好,我是阿里云数据库产品事业部的玄陵,真名郭超. 本次的分享大概分三个部分:Cassandra云数据库简介.Cassandra云数据库特性以及Q&A. 我们先了解一下Cassand ...