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

news2024/11/15 13:50:01

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 method
  if (!destroy_vm || JDK_Version::is_jdk12x_version()) {
    // JSR-166: change call from from ThreadGroup.uncaughtException to
    // java.lang.Thread.dispatchUncaughtException
    if (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 1
                                  uncaught_exception,  // Arg 2
                                  THREAD);
        }
        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 object
    EventThreadEnd event;
    if (event.should_commit()) {
        event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
        event.commit();
    }

    // Call after last event on thread
    EVENT_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 JVMTI
    if (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 happens
  assert(_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 thread
  Threads::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 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);
  lock.notify_all(thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/377414.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++学习笔记-异常处理

一个问题是程序在执行期间产生了一个例外。 C异常是一个特殊的情况在程序运行时&#xff0c;比如试图除以零而引致的响应结果。 异常提供一种方法来从一个程序到另一个程序的一个部分转移控制。 C异常处理建立在三个关键字&#xff1a; try, catch,和 throw。 throw: 程序抛出…

72. import 导入标准模块(os模块)

72. import 导入标准模块(os模块) 文章目录72. import 导入标准模块(os模块)1. 标准模块知识回顾2. os 模块的基本介绍3. import 导入标准模块1. 方法12. 方法21. 导包不同2. 声明不同3. 路径不同4. 概括3. 方法34. 方法44. 调用模块或库中的类、函数、变量5. os模块的路径操作…

Git ---- 国内代码托管中心-码云

Git ---- 国内代码托管中心-码云1. 简介2. 码云账号注册和登录3. 码云创建远程仓库4. IDEA 集成码云1. IDEA 安装码云插件2. IDEA 连接码云5. 码云复制 GitHub 项目1. 简介 众所周知&#xff0c;GitHub 服务器在国外&#xff0c;使用 GitHub 作为项目托管网站&#xff0c;如果…

InnoDB数据页结构__盛放记录的大盒子

一、不同类型的页简介 前边我们简单提了一下页的概念&#xff0c;它是InnoDB管理存储空间的基本单位&#xff0c;一个页的大小一般是16KB。InnoDB为了不同的目的而设计了许多种不同类型的页&#xff0c;比如存放空间头部信息的页&#xff0c;存放Insert Buffer信息的页&#xf…

「TCG 规范解读」TCG 软件栈 TSS (上)

可信计算组织(Ttrusted Computing Group,TCG)是一个非盈利的工业标准组织,它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立,并采纳了由可信计算平台联盟(the Trusted Computing Platform Alliance,TCPA)所开发的规范。现在的规范都不是最终稿,都…

谈谈XR关键技术及VR/AR/MR/XR关系

一、先别被VR/AR/MR/XR搞晕&#xff0c;说说区别虚拟现实&#xff08;Virtual Reality&#xff0c;VR&#xff09;、增强现实&#xff08;Augmented Reality&#xff0c;AR&#xff09;等业务以其三维化、自然交互、空间计算等完全不同于当前移动互联网的特性&#xff0c;被认为…

Kylin V10桌面版arm3568 源码安装redis

上传redis-5.0.14.tar.gz到/home/kylin/下载&#xff1b;解压kylinkylin:~/下载$ tar -zxvf redis-5.0.14.tar.gz/opt下新建redis目录&#xff0c;并将上面解压的文件夹移到此处kylinkylin:~/下载$ sudo mv redis-5.0.14 /opt/redis/编译&#xff1a;kylinkylin:/opt/redis/red…

ACP、PMP、NPDP含金量哪个高?

在自个的领域而言&#xff0c;知名度、报考人数相对较高&#xff0c;这里我就说下他们的含金量吧。&#xff08;含资料&#xff09; 【ACP】 项目管理相关的证书 1、增加项目管理能力&#xff0c;并且对你拥有的知识与技能起到了很好的证明。不断提高自己的能力&#xff0c;不…

软件测试之场景法

场景法 1. 概述 1.1 为什么使用场景法设计测试用例 大多数业务软件由后台管理&#xff08;比如&#xff1a;用户管理、角色管理、权限管理等等各种管理&#xff09;和工作流等几个部分组成。终端用户&#xff0c;期望软件能够实现业务需求&#xff0c;而不是简单的功能的组合…

webpack热更新原理(面试大概率会问)

搭建webpack环境 创建一个项目 mkdir dev-erver && cd dev-server npm init -y // 快速创建一个项目配置 npm i webpack webpack-dev-server webpack-cli --save-dev mkdir src // 创建资源目录 mkdir dist // 输出目录 touch webpack.dev.js // 因为是在开发环境需要…

三层架构+MVC

前言图 什么是三层架构 什么是三层架构 什么是系统架构 所谓系统架构是指&#xff0c;整合应用系统程序大的结构。经常提到的系统结构有两种&#xff1a;三层架构与MVC。这两种结构既有区别&#xff0c;又有联系。但这两种结构的使用&#xff0c;均是为了降低系统模块间的耦合…

这七个100%提高Python代码性能的技巧,一定要知道

B站|公众号&#xff1a;啥都会一点的研究生 相关阅读 整理了几个100%会踩的Python细节坑&#xff0c;提前防止脑血栓 整理了十个100%提高效率的Python编程技巧&#xff0c;更上一层楼 Python-列表&#xff0c;从基础到进阶用法大总结&#xff0c;进来查漏补缺 Python-元组&…

Web Spider案例 网洛克 第二题 JJEncode加密 练习(六)

文章目录一、资源推荐二、逆向目标三、抓包分析 & 下断分析逆向3.1 抓包分析3.2 下断分析逆向四、本地JS代码调试 & 完整JS加密代码4.1 本地JS代码调试4.2 完整JS加密代码五、python具体实现总结提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 …

跬智信息全新推出云原生数据底座玄武,助力国产化数据服务再次升级

2月28日&#xff0c;跬智信息&#xff08;Kyligence&#xff09;宣布全新推出国产化云原生数据底座开源项目玄武&#xff08;XUANWU&#xff09;&#xff0c;以助力企业加速数据平台上云&#xff0c;并实现国产化升级。玄武&#xff08;XUANWU&#xff09;是在容器化技术上形成…

分层测试(2)单元测试【必备】

1. 什么是单元测试&#xff1f; 对代码中的逻辑隔离的最小代码片段进行测试&#xff0c;验证其逻辑是否符合预期&#xff0c;单元可以是函数&#xff0c;方法&#xff0c;类&#xff0c;功能模块。 2. 单元测试的优点 掌握代码&#xff1a;单元测试允许开发人员了解单元提供…

35岁的测试被裁,公司地位还不如00后...

国内的互联网行业发展较快&#xff0c;所以造成了技术研发类员工工作强度比较大&#xff0c;同时技术的快速更新又需要员工不断的学习新的技术。因此淘汰率也比较高&#xff0c;超过35岁的基层研发类员工&#xff0c;往往因为家庭原因、身体原因&#xff0c;比较难以跟得上工作…

python公司企业编码条形码二维码生成系统

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;python编码 免费获取完整源码源文件配置说明教程等 在PyCharm中运行《企业编码生成系统》即可进入如图1所示的系统主界面。在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时&#xff0c;只需要输入…

Javascript的API基本内容(四)

一、日期对象 获取时间戳的方法&#xff0c;分别为 getTime 和 Date.now 和 new Date() // 1. 实例化const date new Date()// 2. 获取时间戳console.log(date.getTime()) // 还有一种获取时间戳的方法console.log(new Date())// 还有一种获取时间戳的方法console.log(Date.n…

maven进阶总结

maven进阶总结1. maven之间可以互相导包2. maven的依赖是具有传递性的3. 可选依赖和排除依赖&#xff1a;4. 继承与聚合4.1聚合4.2 继承5. 属性的配置与使用6. 版本管理8. 多环境开发和跳过测试&#xff08;了解&#xff09;1. maven之间可以互相导包 从当前maven的pom文件中&…

DNS服务器部署的详细操作(图文版)

DNS服务器的部署 打开虚拟机后查看已经开放的端口&#xff0c;可以看到没有TCP53、UDP53&#xff0c;说明DNS服务端口没有打开 打开我的电脑—双击CD驱动器— 选择安装可选的Windows组件 选择网络服务—域名系统&#xff08;DNS&#xff09;— 点击下一步后会弹出如下弹…