当前位置: 首页 > news >正文

thread.join 是干什么的?原理是什么?

Thread.join


加了join,表示join的线程的修改对于join之外的代码是可见的。
代码示例:

public class JoinDemo {private static int i = 1000;public static void main(String[] args) {new Thread(()->{i = 3000;}).start();System.out.println("i="+i);}
}

我们在main线程中定义一个i,初始值为1000。
在main方法中创建一个线程,将i值设置为3000,启动线程。
然后main方法打印 i 的值。
请问,i 值为多少?

我们执行后的输出结果为:

i=1000

这是由于main线程先于thread线程,所以输出的i 值为1000。
如果我们希望 Thread线程中的执行结果对main线程可见,怎么办?
使用 thread.join(),如下所示:

public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{i = 3000;});thread.start();thread.join();System.out.println("i="+i);
}

执行结果:

i=3000

所以说,如果我们希望结果是可见的话,可以通过join来做。
那么join的实现原理是什么?请看下图:
在这里插入图片描述
main线程中,创建了一个线程t1;
t1.start() 启动线程;
t1线程修改了i的值为3000;而且让修改可见。说明t1线程阻塞的main线程,使它无法打印。
t1线程继续执行直到终止。
t1终止后,去唤醒被它阻塞的线程。main线程继续执行,打印出 i=3000

有阻塞,就一定要唤醒,否则线程无法释放。我们是怎么触发唤醒的?
我们进入join的源码:

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;}}}

join是个synchronized的方法,里面有个 wait(0) 方法来阻塞。
那它是如何唤醒的呢?
我们去看hotspot的源码,其中的thread.cpp:

// For any new cleanup additions, please check to see if they need to be applied to
// cleanup_failed_attach_current_thread as well.
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {assert(this == JavaThread::current(),  "thread consistency check");HandleMark hm(this);Handle uncaught_exception(this, this->pending_exception());this->clear_pending_exception();Handle threadObj(this, this->threadObj());assert(threadObj.not_null(), "Java thread object should be created");if (get_thread_profiler() != NULL) {get_thread_profiler()->disengage();ResourceMark rm;get_thread_profiler()->print(get_thread_name());}// FIXIT: This code should be moved into else part, when reliable 1.2/1.3 check is in place{EXCEPTION_MARK;CLEAR_PENDING_EXCEPTION;}// FIXIT: The is_null check is only so it works better on JDK1.2 VM's. This// has to be fixed by a runtime query methodif (!destroy_vm || JDK_Version::is_jdk12x_version()) {// JSR-166: change call from from ThreadGroup.uncaughtException to// java.lang.Thread.dispatchUncaughtExceptionif (uncaught_exception.not_null()) {Handle group(this, java_lang_Thread::threadGroup(threadObj()));{EXCEPTION_MARK;// Check if the method Thread.dispatchUncaughtException() exists. If so// call it.  Otherwise we have an older library without the JSR-166 changes,// so call ThreadGroup.uncaughtException()KlassHandle recvrKlass(THREAD, threadObj->klass());CallInfo callinfo;KlassHandle thread_klass(THREAD, SystemDictionary::Thread_klass());LinkResolver::resolve_virtual_call(callinfo, threadObj, recvrKlass, thread_klass,vmSymbols::dispatchUncaughtException_name(),vmSymbols::throwable_void_signature(),KlassHandle(), false, false, THREAD);CLEAR_PENDING_EXCEPTION;methodHandle method = callinfo.selected_method();if (method.not_null()) {JavaValue result(T_VOID);JavaCalls::call_virtual(&result,threadObj, thread_klass,vmSymbols::dispatchUncaughtException_name(),vmSymbols::throwable_void_signature(),uncaught_exception,THREAD);} else {KlassHandle thread_group(THREAD, SystemDictionary::ThreadGroup_klass());JavaValue result(T_VOID);JavaCalls::call_virtual(&result,group, thread_group,vmSymbols::uncaughtException_name(),vmSymbols::thread_throwable_void_signature(),threadObj,           // Arg 1uncaught_exception,  // Arg 2THREAD);}if (HAS_PENDING_EXCEPTION) {ResourceMark rm(this);jio_fprintf(defaultStream::error_stream(),"\nException: %s thrown from the UncaughtExceptionHandler"" in thread \"%s\"\n",pending_exception()->klass()->external_name(),get_thread_name());CLEAR_PENDING_EXCEPTION;}}}// Called before the java thread exit since we want to read info// from java_lang_Thread objectEventThreadEnd event;if (event.should_commit()) {event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));event.commit();}// Call after last event on threadEVENT_THREAD_EXIT(this);// Call Thread.exit(). We try 3 times in case we got another Thread.stop during// the execution of the method. If that is not enough, then we don't really care. Thread.stop// is deprecated anyhow.if (!is_Compiler_thread()) {int count = 3;while (java_lang_Thread::threadGroup(threadObj()) != NULL && (count-- > 0)) {EXCEPTION_MARK;JavaValue result(T_VOID);KlassHandle thread_klass(THREAD, SystemDictionary::Thread_klass());JavaCalls::call_virtual(&result,threadObj, thread_klass,vmSymbols::exit_method_name(),vmSymbols::void_method_signature(),THREAD);CLEAR_PENDING_EXCEPTION;}}// notify JVMTIif (JvmtiExport::should_post_thread_life()) {JvmtiExport::post_thread_end(this);}// We have notified the agents that we are exiting, before we go on,// we must check for a pending external suspend request and honor it// in order to not surprise the thread that made the suspend request.while (true) {{MutexLockerEx ml(SR_lock(), Mutex::_no_safepoint_check_flag);if (!is_external_suspend()) {set_terminated(_thread_exiting);ThreadService::current_thread_exiting(this);break;}// Implied else:// Things get a little tricky here. We have a pending external// suspend request, but we are holding the SR_lock so we// can't just self-suspend. So we temporarily drop the lock// and then self-suspend.}ThreadBlockInVM tbivm(this);java_suspend_self();// We're done with this suspend request, but we have to loop around// and check again. Eventually we will get SR_lock without a pending// external suspend request and will be able to mark ourselves as// exiting.}// no more external suspends are allowed at this point} else {// before_exit() has already posted JVMTI THREAD_END events}// 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);assert(!this->has_pending_exception(), "ensure_join should have cleared");// 6282335 JNI DetachCurrentThread spec states that all Java monitors// held by this thread must be released.  A detach operation must only// get here if there are no Java frames on the stack.  Therefore, any// owned monitors at this point MUST be JNI-acquired monitors which are// pre-inflated and in the monitor cache.//// ensure_join() ignores IllegalThreadStateExceptions, and so does this.if (exit_type == jni_detach && JNIDetachReleasesMonitors) {assert(!this->has_last_Java_frame(), "detaching with Java frames?");ObjectSynchronizer::release_monitors_owned_by_thread(this);assert(!this->has_pending_exception(), "release_monitors should have cleared");}// These things needs to be done while we are still a Java Thread. Make sure that thread// is in a consistent state, in case GC happensassert(_privileged_stack_top == NULL, "must be NULL when we get here");if (active_handles() != NULL) {JNIHandleBlock* block = active_handles();set_active_handles(NULL);JNIHandleBlock::release_block(block);}if (free_handle_block() != NULL) {JNIHandleBlock* block = free_handle_block();set_free_handle_block(NULL);JNIHandleBlock::release_block(block);}// These have to be removed while this is still a valid thread.remove_stack_guard_pages();if (UseTLAB) {tlab().make_parsable(true);  // retire TLAB}if (JvmtiEnv::environments_might_exist()) {JvmtiExport::cleanup_thread(this);}// We must flush any deferred card marks before removing a thread from// the list of active threads.Universe::heap()->flush_deferred_store_barrier(this);assert(deferred_card_mark().is_empty(), "Should have been flushed");#if INCLUDE_ALL_GCS// We must flush the G1-related buffers before removing a thread// from the list of active threads. We must do this after any deferred// card marks have been flushed (above) so that any entries that are// added to the thread's dirty card queue as a result are not lost.if (UseG1GC) {flush_barrier_queues();}
#endif // INCLUDE_ALL_GCS// Remove from list of active threads list, and notify VM thread if we are the last non-daemon threadThreads::remove(this);
}

线程终止的时候,会调用JavaThread::exit 方法,这个退出方法中,有个清理的工作:

  // 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);

唤醒当前线程对象上的阻塞线程,这是在调用exit之后被完成的。
我们来看 ensure_join 方法具体做了些什么?

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 anywaythread->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 belowjava_lang_Thread::set_thread(threadObj(), NULL);lock.notify_all(thread);// Ignore pending exception (ThreadDeath), since we are exiting anywaythread->clear_pending_exception();
}

其中关键的是 lock.notify_all(thread); wait方法锁的是当前t1实例,t1退出的时候,拿到t1实例,然后拿到t1的锁,然后notify_all。

以上就是我们对thread.join()的全部解读。

相关文章:

thread.join 是干什么的?原理是什么?

Thread.join 加了join&#xff0c;表示join的线程的修改对于join之外的代码是可见的。 代码示例&#xff1a; public class JoinDemo {private static int i 1000;public static void main(String[] args) {new Thread(()->{i 3000;}).start();System.out.println("…...

论文阅读 | Cross-Attention Transformer for Video Interpolation

前言&#xff1a;ACCV2022wrokshop用transformer做插帧的文章&#xff0c;q&#xff0c;kv&#xff0c;来自不同的图像 代码&#xff1a;【here】 Cross-Attention Transformer for Video Interpolation 引言 传统的插帧方法多用光流&#xff0c;但是光流的局限性在于 第一&…...

【C++修炼之路】22.哈希

每一个不曾起舞的日子都是对生命的辜负 哈希一.哈希概念及性质1.1 哈希概念1.2 哈希冲突1.3 哈希函数二.哈希冲突解决2.1 闭散列/开放定址法2.2 开散列/哈希桶三.开放定址法代码3.1 插入Insert3.2 查找Find3.3 删除Erase3.4 映射的改良&完整代码四.开散列代码4.1 插入Inser…...

HashMap原理(一):哈希函数的设计

目录导航哈希函数的作用与本质哈希函数设计哈希表初始容量的校正哈希表容量为2的整数次幂的缺陷及解决办法注&#xff1a;为了简化代码&#xff0c;提高语义&#xff0c;本文将HashMap很多核心代码抽出并根据代码含义为代码片段取名&#xff0c;完全是为了方便读者理解。哈希函…...

06--WXS 脚本

1、简介WXS&#xff08;WeiXin Script&#xff09;是小程序的一套脚本语言&#xff0c;结合 WXML &#xff0c;可以构建出页面的结构。 注意事项WXS 不依赖于运行时的基础库版本&#xff0c;可以在所有版本的小程序中运行。WXS 与 JavaScript 是不同的语言&#xff0c;有自己的…...

【Vue3】vue3 + ts 封装城市选择组件

城市选择-基本功能 能够封装城市选择组件&#xff0c;并且完成基础的显示隐藏的交互功能 &#xff08;1&#xff09;封装通用组件src/components/city/index.vue <script lang"ts" setup name"City"></script> <template><div class…...

C语言if判断语句的三种用法

C if 语句 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 语法 C 语言中 if 语句的语法&#xff1a; if(boolean_expression) {/* 如果布尔表达式为真将执行的语句 */ }如果布尔表达式为 true&#xff0c;则 if 语句内的代码块将被执行。如果布尔表达式为 false&…...

React中echarts的封装

做大屏的时候经常会遇到 echarts 展示 在 React &#xff08;^18.2.0&#xff09; 中对 echarts &#xff08;^5.4.0&#xff09; 的简单封装 echarts 封装使用 props 说明 参数说明类型可选值默认值opts初始化传入的 opts https://echarts.apache.org/zh/api.html#echarts…...

IV测试系统3A太阳能模拟器在光伏中应用

一、概述IV测试系统3A太阳能模拟器应具备光束准直、光斑均匀、辐照稳定、且与太阳光谱匹配的特点&#xff0c;使用户可足不出户的完成需要太阳光照条件的测试。科迎法电气提供多规格高品质的太阳模拟器&#xff0c;可适用于单晶硅、多晶硅、非晶硅、染料敏化、有机、钙钛矿等各…...

Vue 中过滤器 filter 使用教程

Vue 过滤器 filter 使用教程文章目录Vue 过滤器 filter 使用教程一、过滤器1.1 过滤器使用的背景1.2 格式化时间的不同实现1.3 过滤器的使用1.4 过滤器总结一、过滤器 1.1 过滤器使用的背景 过滤器提供给我们的一种数据处理方式。过滤器功能不是必须要使用的&#xff0c;因为它…...

源码numpy笔记

参考文章 numpy学习 numpy中的浅复制和深复制的详细用法 numpy中的np.where torch.gather() Numpy的核心数据结构&#xff0c;就叫做array就是数组&#xff0c;array对象可以是一维数组&#xff0c;也可以是多维数组 array本身的属性 shape&#xff1a;返回一个元组&#xf…...

【VUE】六 路由和传值

目录 一、 路由和传值 二、案例 三、案例存在无法刷新问题 一、 路由和传值 当某个组件可以根据某些参数值的不同&#xff0c;展示不同效果时&#xff0c;需要用到动态路由。 例如&#xff1a;访问网站看到课程列表&#xff0c;点击某个课程&#xff0c;就可以跳转到课程详…...

ChatGPT修炼指南和它的电力畅想

近期&#xff0c;ChatGPT刷屏各大社交平台&#xff0c;无疑成为人工智能界最靓的仔&#xff01; 身为一款“会说话”的聊天机器人程序&#xff0c;它与前辈产品Siri、小度、微软小冰等有什么不同&#xff1f;先来听听小伙伴们怎么说。 ChatGPT何以修炼得这么强大&#xff1f;…...

基于vscode开发vue项目的详细步骤教程

1、Vue下载安装步骤的详细教程(亲测有效) 1_水w的博客-CSDN博客 2、Vue下载安装步骤的详细教程(亲测有效) 2 安装与创建默认项目_水w的博客-CSDN博客 目录 五、vscode集成npm开发vue项目 1、vscode安装所需要的插件&#xff1a; 2、搭建一个vue小页面(入门vue) 3、大致理解…...

【C++初阶】1. C++入门

1. 前言 1. 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; 20世纪80年代&#xff0c; 计算机界提出了OOP(…...

数据结构与算法(二十)快速排序、堆排序(四)

数据结构与算法&#xff08;三&#xff09;软件设计(十九)https://blog.csdn.net/ke1ying/article/details/129252205 排序 分为 稳定排序 和 不稳定排序 内排序 和 外排序 内排序指在内存里&#xff0c;外排序指在外部存储空间排序 1、排序的方法分类。 插入排序&#xff…...

TensorRT量化工具pytorch_quantization代码解析(二)

有些地方看的不是透彻&#xff0c;后续继续补充&#xff01; 继续看张量量化函数&#xff0c;代码位于&#xff1a;tools\pytorch-quantization\pytorch_quantization\tensor_quant.py ScaledQuantDescriptor 量化的支持描述符:描述张量应该如何量化。QuantDescriptor和张量…...

buu [BJDCTF2020]easyrsa 1

题目描述 &#xff1a; from Crypto.Util.number import getPrime,bytes_to_long from sympy import Derivative from fractions import Fraction from secret import flagpgetPrime(1024) qgetPrime(1024) e65537 np*q zFraction(1,Derivative(arctan(p),p))-Fraction(1,Deri…...

taobao.user.openuid.getbyorder( 根据订单获取买家openuid )

&#xffe5;免费不需用户授权 根据订单获取买家openuid&#xff0c;最大查询30个 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey, secret); UserOpenuidGetbyorderR…...

Mac iTerm2 rz sz

1、安装brew&#xff08;找了很多&#x1f517;&#xff0c;就这个博主的好用&#xff09; Mac如何安装brew&#xff1f;_行走的码农00的博客-CSDN博客_mac brew 2、安装lrzsz brew install lrzsz 检查是否安装成功 brew list 定位lrzsz的安装目录 brew list lrzsz 执…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…...