当前位置: 首页 > 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()的全部解读。

http://www.lryc.cn/news/23622.html

相关文章:

  • 论文阅读 | Cross-Attention Transformer for Video Interpolation
  • 【C++修炼之路】22.哈希
  • HashMap原理(一):哈希函数的设计
  • 06--WXS 脚本
  • 【Vue3】vue3 + ts 封装城市选择组件
  • C语言if判断语句的三种用法
  • React中echarts的封装
  • IV测试系统3A太阳能模拟器在光伏中应用
  • Vue 中过滤器 filter 使用教程
  • 源码numpy笔记
  • 【VUE】六 路由和传值
  • ChatGPT修炼指南和它的电力畅想
  • 基于vscode开发vue项目的详细步骤教程
  • 【C++初阶】1. C++入门
  • 数据结构与算法(二十)快速排序、堆排序(四)
  • TensorRT量化工具pytorch_quantization代码解析(二)
  • buu [BJDCTF2020]easyrsa 1
  • taobao.user.openuid.getbyorder( 根据订单获取买家openuid )
  • Mac iTerm2 rz sz
  • 高通平台开发系列讲解(Sensor篇)Gsensor基础知识
  • 图像处理实战--Opencv实现人像迁移
  • OnlyOffice验证(二)在Centos7上部署OnlyOffice编译结果
  • 6.补充和总结【Java面试第三季】
  • 基于ssm框架大学生社团管理系统(源码+数据库+文档)
  • vulnhub靶场NAPPING: 1.0.1教程
  • Docker基本介绍
  • 可用于标记蛋白质216699-36-4,6-ROX,SE,6-羧基-X-罗丹明琥珀酰亚胺酯
  • 高数:极限的定义
  • 大数据技术之Hadoop
  • 一文带你搞懂Go语言函数选项模式,Go函数一等公民。