JVM源码剖析之线程的创建过程

news2024/11/15 20:55:19

说在前面:

对于Java线程的创建这个话题,似乎已经被"八股文"带偏~ 大部分Java程序员从"八股文"得知创建Java线程有N种方式,比如new Thread、new Runnable、Callable、线程池等等~ 而笔者写下这篇文章的目的是让大家从JVM源码的层面知道创建一个Java线程的方式。

版本信息:
jdk版本:jdk8u40

源码剖析:

public
class Thread implements Runnable {}

从Thread类的继承关系来看,Thread类实现Runnable接口,不少读者可能不明白为什么需要实现Runnable接口,也不明白Thread和Runnable之间的关系,那么笔者不妨以自己见解解释一番~

Thread:Java层面开发者使用的类,开发者可以使用它创建、启动、关闭线程等等操作

Runnable:一个函数式接口,无返回值、无入参,用于回调,其中编写开发者的回调逻辑(逻辑入口)

Callable:一个函数式接口,有返回值、无入参,用于回调,其中编写开发者的回调逻辑(逻辑入口)

所以Thread和Runnable之间的关系一目了然,一个是Java线程的API集合,一个是定义了逻辑入口的接口

为什么Thread需要实现Runnable,因为当线程启动后,Thread需要帮开发者自动执行线程执行体,所以需要一个执行入口方法,刚好Runnable接口就提供了此入口~

接下来看一下Thread的构造方法。

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {

            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        g.checkAccess();

        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

        this.stackSize = stackSize;
        tid = nextThreadID();
    }

构造方法虽然比较长,但是都是对当前线程的属性赋值,并没有其他任何操作,所以笔者连注释都没有写。

写到这里,我们需要理解一个点,Java是没有能力创建底层的线程,这个需要交给JVM来创建,而这里new Thread创建的仅仅是Java层面表示的线程对象~

而JVM来创建线程,所以需要Java层面调用native方法,所以我们看到Thread中start方法。

public synchronized void start() {
		// 此线程的状态如果不是创建状态就直接抛出非法逻辑异常。
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        // 添加到对应的线程组中。
        group.add(this);

        boolean started = false;
        try {
        	// Native方法,此方法会创建底层的线程,并且启动线程,并且执行线程的执行体(Runnable的run方法)
            start0();
            started = true;		// 正常启动完毕。
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {

            }
        }
    }

private native void start0();

start方法会去底层创建线程,并且启动线程,执行线程的执行体 ,所以需要看到start0这个native方法的实现,所以下面是C/C++的代码~

{"start0",           "()V",        (void *)&JVM_StartThread}

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;

  bool throw_illegal_thread_state = false;

  {
    MutexLocker mu(Threads_lock);

    // 获取到Java开发者设置的栈大小,这个参数一般不会设置
    jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));

    size_t sz = size > 0 ? (size_t) size : 0;

    // 创建底层的线程。
    // 当创建好线程后,会回调thread_entry此方法。
    native_thread = new JavaThread(&thread_entry, sz);

    if (native_thread->osthread() != NULL) {
      // 初始化线程,比如是否是守护线程,线程优先级。以及线程数量的统计。
      native_thread->prepare(jthread);
    }
  }

  // 设置成运行中状态
  Thread::start(native_thread);

JVM_END

因为JVM是c/c++编写,而c++是有面向对象的思想存在,所以在new JavaThread中,会执行构造方法,我们看到构造方法。

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()	// 调用父类构造方法
{
	// 初始化参数
  initialize();
  _jni_attach_state = _not_attaching_via_jni;
  // 设置执行入口,当底层线程创建好以后,会去回调此方法
  set_entry_point(entry_point);		

  // 设置此线程的类型,当前线程是java_thread类型
  os::ThreadType thr_type = os::java_thread;
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread;
  // 真正的线程只有操作系统才有权利去创建,所以这里调用操作系统类库去创建。
  os::create_thread(this, thr_type, stack_sz);
  _safepoint_visible = false;

}

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
  assert(thread->osthread() == NULL, "caller responsible");

  OSThread* osthread = new OSThread(NULL, NULL);

  osthread->set_thread_type(thr_type);

  osthread->set_state(ALLOCATED);

  thread->set_osthread(osthread);

  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

 
 	…………	// 省略一部分设置参数代码

  // glibc guard page
  pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));

  ThreadState state;

  {

    pthread_t tid;
    // java_start 作为统一的入口,后续再根据thread类型做分发(多态)。
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

    pthread_attr_destroy(&attr);

    osthread->set_pthread_id(tid);

    // Wait until child thread is either initialized or aborted
    {
      Monitor* sync_with_child = osthread->startThread_lock();
      MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
      while ((state = osthread->get_state()) == ALLOCATED) {
        sync_with_child->wait(Mutex::_no_safepoint_check_flag);
      }
    }

    if (lock) {
      os::Linux::createThread_lock()->unlock();
    }
  }

  return true;
}

因为JVM是帮Java开发者实现了跨平台机制,所以需要适配所有OS平台,而我们只关心Linux操作系统,而Linux操作系统使用的是POSIX的线程标准,实现者肯定是Glibc类库,实现就是PThread线程库,而这里也是调用了pthread_create库函数创建了底层的线程(这里不懂没关系,只需要明白会去操作系统创建线程),并且设置线程的执行入口是java_start方法。

// pthread线程回调执行点。
static void *java_start(Thread *thread) {
  
  …………  // 省略大部分的设置参数和状态值的代码,方便观看核心源码

  // 这里是区分JVM层面抽象的不同线程的不同执行点(多态)
  // 而这里thread对象是JavaThread
  thread->run();

  return 0;
}

void JavaThread::run() {
  
  ………… // 省略大部分的设置参数和状态值的代码,方便观看核心源码

  // 执行设置的入口
  thread_main_inner();
}

这里是pthread线程启动后回调的方法,这里会回调JavaThread的thread_main_inner方法。这里如果能看懂C++的读者会发现,这里使用多态思想非常完美~

void JavaThread::thread_main_inner() {

	// 不存在异常
  if (!this->has_pending_exception() &&
      !java_lang_Thread::is_stillborn(this->threadObj())) {

    HandleMark hm(this);
    // 回调设置的入口
    this->entry_point()(this, this);
  }

  DTRACE_THREAD_PROBE(stop, this);

  // 执行到这里代表线程执行结束了,需要做释放工作。
  this->exit(false);
  delete this;
}

 经过一系列的回调,最终来到thread_entry方法。

static void thread_entry(JavaThread* thread, TRAPS) {
  HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  JavaCalls::call_virtual(&result,
                          obj,
                          KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                          vmSymbols::run_method_name(),
                          vmSymbols::void_method_signature(),
                          THREAD);
}

这里非常的简单,在JVM层面调用Java的方法,而这里调用的就是Thread类中run方法(也即调用Runnable接口的run方法)

@Override
public void run() {
    // target参数往往是开发者传入的Runnable接口的实现类。
    if (target != null) {
        target.run();        // 调用开发者的逻辑
    }
}

上面的流程对于不懂JVM源码和不懂C++语言的读者来说非常吃力,甚至看不懂,这也很正常,源码层面是这样。但是读者会给你们做一个总结:

  1. 在Java层面调用Thread类的start方法
  2. start方法是一个native方法,会在c++层面创建JavaThread对象(因为c++也是面向对象)
  3. 在JavaThread的构造方法中会去创建OsThread对象(因为JVM是跨平台,存在很多个操作系统平台,所以需要一个OsThread做高度抽象)
  4. 在Linux操作系统平台中会去创建pthread线程(此线程可以理解为就是操作系统层面的线程)
  5. 创建完pthread线程后会经过一层一层的回调方法,最终回调thread_entry方法
  6. thread_entry方法中,会从JVM层面调用Java层面的方法,而调用的方法是Thread类中run方法,而Thread类是实现Runnable接口,所以也证明了文章开头说的Runnable 接口是作为一个执行入口
  7. 在Thread的run方法中,会去执行用户传入的Runnable接口,或者开发者重写了Thread的run方法。总之,这里就是回调开发者的逻辑。
  8. 所以Java的Thread创建入口就只有一个,就是Java的Thread类的start方法。

线程的关系图如上所述。

其实,你会发现,没有什么是中间抽一层不能解决的,每一层有每一层的职责,但是往往抽一层就需要有中间层的表示状态~

总结:

因为是深入到JVM源码层面,所以部分代码很多读者看不懂。但是代码是科学,他并不是神学,要论证真实性,必须要深入到源码层面。

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

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

相关文章

Qemu系统模拟:1 简介

目录 1 后端/加速器2 特性简介3 运行 1 后端/加速器 系统模拟主要用于在host设备上运行guest OSQEMU支持多种hypervisors,同时也支持JIT模拟方案(TCG) 例如从上表我们可以看出,运行在x86硬件上的Linux系统支持KVM,Xen,TCG 2 特性简介 提供…

大数据集群(Hadoop生态)安装部署

目录 1. 简介 2. 前置要求 3. Hadoop集群角色 4. 角色和节点分配 5. 调整虚拟机内存 6. Zookeeper集群部署 7. Hadoop集群部署 7.1 下载Hadoop安装包、解压、配置软链接 7.2 修改配置文件:hadoop-env.sh 7.3 修改配置文件:core-site…

自动化机器人的开发框架

自动化领域有多个开源的框架和工具,用于自动化各种任务和流程。以下是一些常见的自动化开源框架和工具,以及它们的特点,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1.Robot…

【Java】Java中BigDecimal解决精度丢失问题

文章目录 1.我们先看一个例子2.BigDecimal中的一些方法3.值得注意的是如果要想无精度丢失的情况下计算结果,那么需要把double,float类型的参数转化为String类型的。并且使用BigDecimal(String)这个构造方法进行构造。 去获取结果。不然还是没有效果。4.在一般开发过…

python curl2pyreqs 生成接口脚本

下载 curl2pyreqs 库 pip install curl2pyreqs -i https://pypi.tuna.tsinghua.edu.cn/simple 打开调试模式,在Network这里获取 接口的cURL 打开cmd窗口,输入curl2pyreqs,会自动生成接口代码 curl2pyreqs 执行接口脚本,返回响应…

Docker基础操作容器

启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(exited)的容器重新启动。 因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。 新建并启动 所需要的命令主要…

火爆全网的头戴式耳机,Y2K辣妹时髦单品——Umelody轻律 U1头戴式耳机!

近些年,“复古”这阵风在时尚圈是越刮越猛。Y2K穿搭风更是火爆,最近火爆的Umelody轻律 U1头戴式耳机,可以说是通过单品来就能掌握其中的精髓,在众多博主和达人的穿搭中轮番上阵,无论是挂脖还是佩戴都特别好看&#xff…

STM32单片机入门学习(五)-按钮控制LED

按钮和LED接线 LED负极接B12,正极接VCC 按钮一端接B13,一端接GND,按下为低电平,松开为高电平 如图: 主程序代码:main.c #include "stm32f10x.h" #include "Delay.h" //delay函数所在头文件 #include …

基于Springboot实现影视影院订票选座管理系统【项目源码+论文说明】

基于Springboot实现影视影院订票选座管理系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个影城管理系统 ,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论…

域信息收集

DMZ,是英文“demilitarized zone”的缩写,中文名称为“隔离区”,也称“非军事化区”。它是为了解决安装防火墙后外部网络的访问用户不能访问内部网络服务器的问题,而设立的一个非安全系统与安全系统之间的缓冲区。该缓冲区位于企业…

Git Pull failure 【add/commit】

操作页面 操作步骤 1. 打开项目所在 在.git目录下右击打开Git Bssh Here 2. git add . 3. git commit -m "提交" 4. 成功提交到本地, 这下就可以拉取代码了

选择适合您的项目管理软件:哪个更好?

对于项目管理而言,一个优秀的项目管理工具可以达到事半功倍的效果。Zoho Projects 是一款功能强大,适用于各行业的标准化项目管理工具,具备适用度高的特点,丰富的功能模块可以匹配各行各业的项目管理。 一,任务管理 通…

JUC第十八讲:JUC集合-BlockingQueue 详解

JUC集合-BlockingQueue 详解 JUC里的 BlockingQueue 接口表示一个线程安全放入和提取实例的队列。本文是JUC第十八讲,将给你演示如何使用这个 BlockingQueue,不会讨论如何在 Java 中实现一个你自己的 BlockingQueue。 文章目录 JUC集合-BlockingQueue 详…

Linux是什么,有哪些特点?Linux和UNIX的关系及区别(详解版)

与大家熟知的 Windows 操作系统软件一样,Linux 也是一个操作系统软件,其 logo 是一只企鹅(如图 1 所示)。与 Windows 不同之处在于,Linux 是一套开放源代码程序的、可以自由传播的类 Unix 操作系统软件。 图 1 Linux 操…

Atcoder Regular Contest 166

只打了半场。 A. Replace C or Swap AB 首先如果存在某个 \(i\),使得 \(Y_i\) 是 C 且 \(X_i\) 不是,那么显然是不合法的,可以直接判掉。 那么除去上述情况 \(Y\) 中为字符 C 的位置 \(X\) 也只能是 C。它们把字符串分成了若干段,…

LVGL_基础控件timer

LVGL_基础控件timer 1、创建基础控件定时器 /* 下面创建三个timer,最后创建的timer会被放在timer list的最前面, lvgl的任务处理器会从timer list从头到尾按顺序遍历检查,执行满足执行条件的timer。 因此,如果三个定时器的周期设…

帮微软语音助手纠正“阿弥陀佛”“e”字错误发音的技巧

一、前言 微软AI文字转语音助手,现已被大家普便应用。最近在传统文化佛学名词的发音转换应用中,发现了一个致命的错误。那就是“阿弥陀佛”中的“阿”字的“a”发音,被误读为“e”。说起这个重大的错误,佛门大德南怀瑾老师也一再…

排序算法——选择排序

一、介绍: 选择排序就是按照一定的顺序从选取第一个元素索引开始,将其储存在一个变量值中,根据排序规则比较后边每一个元素与这个元素的大小,根据排序规则需要,变量值的索引值进行替换,一轮遍历之后&#x…

【RabbitMQ】docker rabbitmq集群 docker搭建rabbitmq集群

docker rabbitmq集群 docker搭建rabbitmq集群 RabbitMQ提供了两种常用的集群模式 1.普通集群模式 2.镜像集群模式 普通集群模式只能同步主节点上的交换机和队列信息,但对于队列中的消息不做同步,主节点宕机也不能进行切换(故障转移&#xff…

socket can查看详细信息 命令 ip -details -statistics link show can0

ip -details -statistics link show can0 ip -details link show can0 ip -statistics link show can0 也可以像第一行那样结合使用