由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& ...
随机推荐
- 【转载】基于dom的一些前端漏洞
最直接的xss --dom xss function trackSearch(query) { document.write('<img src="/resources/images/ ...
- vue学习(十) v-for循环普通数组 、对象数组、 迭代数字
//html <div id="app"> <p v-for="item in list">{{item}}</p> < ...
- Java容器学习之ArrayList
一.概述 ArrayList是java中十分常用的集合类,继承于AbstractList,并实现了List.RandomAccess.Cloneable和Serializable接口.ArrayLis ...
- 交通网最短路径长 dp c++
// // Created by Arc on 2020/4/26. // /* * 城市交通线路,单向通行,道路之间可能有不通,每条连同的路之间都有相应的费用 * 求从起点到终点的最小费用 * * ...
- IT运维面试问题总结
IT运维面试总结如下,后期更新于:https://www.yuque.com/docs/share/d3dd1e8e-6828-4da7-9e30-6a4f45c6fa8e. 欢迎基于学习.交流目的的 ...
- 最基本的Tkinter界面操作
1.创建应用程序主窗口对象 root = Tk() 2.在主窗口中,添加各种可视化组件 btn1 = Button(root) btn1["text"] = "点我&qu ...
- PHP getName() 函数
实例 返回 XML 元素及其子元素的名称: <?php$xml=<<<XML高佣联盟 www.cgewang.com<?xml version="1.0&quo ...
- 4.24 省选模拟赛 欧珀瑞特 主席树 可持久化trie树
很容易的一道题目.大概.不过我空间计算失误MLE了 我草草的计算了一下没想到GG了. 关键的是 我学了一个dalao的空间回收的方法 但是弄巧成拙了. 题目没有明确指出 在任意时刻数组长度为有限制什么 ...
- 全程干货,requests模块与selenium框架详解
requests模块 前言: 通常我们利用Python写一些WEB程序.webAPI部署在服务端,让客户端request,我们作为服务器端response数据: 但也可以反主为客利用Python的re ...
- Windows聚焦失效问题的解决办法
1. 设置Windows聚焦 步骤:任务栏右键 → 任务栏设置 → 锁屏界面 → 背景选择Windows聚焦 2. 解决Windows聚焦失效问题 设置完Windows聚焦之后,锁屏界面却没有变. 尝 ...