目录

一:Java线程介绍

二:Java线程入口分析

三:Java线程的创建

四:总结


周末抽了点时间,研究了下HotSpot是如何创建Java线程的,顺便总结一下。文中引用的源码里删除很多细节,只保留了主要过程,感兴趣的同学可以自己去深入研究。能力有限,错误地方恳请指正。

一:Java线程介绍

做Java开发或者Android的同学,肯定对Java线程很熟悉。之前在阅读一些JVM相关的书时,了解到Java线程其实是映射到操作系统的内核线程上的,所以Java线程基本上也就是操作系统在进行管理。在Linux中,线程和进程用的是同一个结构体进行描述的,只不过进程拥有自己独立的地址空间,而同一个进程的线程之间是共享资源的。

二:Java线程入口分析

想要启动一个Java线程,主要有两种方法,第一种可以实现一个继承自Thread的子类,重写run();第二种可以实现一个Runnable,交给Thread执行。这两种方法都很简便,我们可以根据自己的业务需求选择,但是无论选择哪种方法,都需要调用Thread.start(),才能真正的启动一个异步线程进行工作,笔者刚上大学接触Java时,曾经天真的以为调用Thread.run()就行了,其实直接调用run(),JVM并不会去创建一个线程,run()只会工作在原有线程,和调用普通对象的方法没有任何区别。

上面提到了要想启动线程,必须要调用Thread.start(),那么这个方法便是我们研究的入口了。

   public synchronized void start() {if (threadStatus != 0)throw new IllegalThreadStateException();group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}}private native void start0();

通过上面的代码可以得知,start()方法中做了一些线程状态判断等工作,但是真正启动Java线程的地方是调用了start0(),start0()是一个Native方法。start0()是何处实现的呢?我们先来看看Thread.c 中的一段定义:

static JNINativeMethod methods[] = {{"start0",           "()V",        (void *)&JVM_StartThread},{"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},{"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},{"suspend0",         "()V",        (void *)&JVM_SuspendThread},{"resume0",          "()V",        (void *)&JVM_ResumeThread},{"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},{"yield",            "()V",        (void *)&JVM_Yield},{"sleep",            "(J)V",       (void *)&JVM_Sleep},{"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},{"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},{"interrupt0",       "()V",        (void *)&JVM_Interrupt},{"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},{"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},{"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},{"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},{"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

上面定义了一个数组,数组中存放的为JNINativeMethod类型的结构体变量,JNINativeMethod定义在jni.h中:

typedef struct {char *name;char *signature;void *fnPtr;
} JNINativeMethod;

JNINativeMethod主要是进行一个jni方法的映射关系,将native方法和真正的实现方法进行绑定。那么具体是何时进行绑定的呢?java层的Thread在类的静态初始化块中,调用了registerNatives()方法:

   private static native void registerNatives();static {registerNatives();}

我们来看看registerNatives()对应的Jni方法:

JNIEXPORT void JNICALL Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

可见此时会将上述数组methods中的方法映射关系进行注册。注册好了后,我们再来看看上面定义的数组,这里面将Java线程中的很多native方法与实现其功能的方法指针进行了绑定,比如start0()就和JVM_StartThread进行了绑定。所以我们想研究start0(),就直接看看JVM_StartThread指针所指向的方法即可。

三:Java线程的创建

JVM_StartThread定义在jvm.cpp中:

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);if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {throw_illegal_thread_state = true;} else {jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));size_t sz = size > 0 ? (size_t) size : 0;native_thread = new JavaThread(&thread_entry, sz);if (native_thread->osthread() != NULL) {native_thread->prepare(jthread);}}}......Thread::start(native_thread);JVM_END

源码中本来有很多英文注释,我这里先给删了,下面来一步步分析下上面的代码吧。

1:判断Java线程是否已经启动,如果已经启动过,则会抛异常。

if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {throw_illegal_thread_state = true;}

2: 如果第一步判断中,Java线程没有启动过,则会开始创建Java线程。

jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz);

Java线程的创建过程主要就在JavaThread的构造函数中:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :Thread()
{initialize();_jni_attach_state = _not_attaching_via_jni;set_entry_point(entry_point);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);
}

最后一句os::create_thread(this, thr_type, stack_sz) 便开始真正的创建Java线程对应的内核线程。

bool os::create_thread(Thread* thread, ThreadType thr_type,size_t req_stack_size) {......pthread_t tid;int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);......return true;
}

上面这个方法主要就是利用pthread_create()来创建线程。其中第三个参数thread_native_entry便是新起的线程运行的初始地址,其为定义在os_bsd.cpp中的一个方法指针,而第四个参数thread即thread_native_entry的参数:

static void *thread_native_entry(Thread *thread) {......thread->run();......return 0;
}

新线程创建后就会从thread_native_entry()开始运行,thread_native_entry()中调用了thread->run():

// thread.cpp
void JavaThread::run() {......thread_main_inner();
}

此方法最后调用了thread_main_inner():

// thread.cpp
void JavaThread::thread_main_inner() {if (!this->has_pending_exception() &&!java_lang_Thread::is_stillborn(this->threadObj())) {{ResourceMark rm(this);this->set_native_thread_name(this->get_thread_name());}HandleMark hm(this);this->entry_point()(this, this);}DTRACE_THREAD_PROBE(stop, this);this->exit(false);delete this;
}

我们重点关注下this->entry_point()(this, this),entry_point()返回的其实就是在 new JavaThread(&thread_entry, sz) 时传入的thread_entry。这里就相当于调用了thread_entry(this,this)。thread_entry定义在jvm.cpp中:

// jvm.cpp
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);
}

哈哈,这里见到了一个老朋友:JavaCalls。JavaCalls模块是用来调用Java方法的。不了解的朋友可以直接看之前写的文章《JVM方法执行的来龙去脉》。我们来看看这里调用JavaCalls::call_virtual()传入的一些参数:

  • obj:Java线程对象;
  • KlassHandle(THREAD, SystemDictionary::Thread_klass()):Java线程类,记录在SystemDictionary中,即java_lang_Thread;
  • vmSymbols::run_method_name():即"run";
  • vmSymbols::void_method_signature():即"()V";

经过上面的分析,这里其实就是开始调用Java线程对象的run()方法。

3:开始执行所创建的内核线程,即从第二步所说的thread_entry处开始执行。

Thread::start(native_thread);

四:总结

到这里Java线程就已经真正的运行起来了,总结下上面的过程:

  • 1:调用Java线程start()方法,通过jni方式,调用到JVM层。
  • 2:JVM通过pthread_create()创建一个系统内核线程,并指定内核线程的初始运行地址,即一个方法指针。
  • 3:在内核线程的初始运行方法中,利用JavaCalls模块,调用java线程的run()方法,开始java级别的线程执行。

推荐阅读:

Java编程思想之多线程(三)

作者:半栈工程师
链接:https://www.jianshu.com/p/0dd2c5e2d63b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Java并发编程—Thread类的start()方法是如何启动一个线程的?相关推荐

  1. Java并发编程原理与实战六:主线程等待子线程解决方案

    Java并发编程原理与实战六:主线程等待子线程解决方案 参考文章: (1)Java并发编程原理与实战六:主线程等待子线程解决方案 (2)https://www.cnblogs.com/pony1223 ...

  2. Java并发编程工具类:CountDownLatch、CyclicBarrier、Semaphore

    在jdk5中,java提供了一些非常有用的辅助工具类,包括CountDownLatch和CyclicBarrier(两者都可以实现线程之间的通信).Semaphore(控制方法被线程访问的数量),他们 ...

  3. Java并发编程:从源码分析几道必问线程池的面试题?

    引言 上一篇文章我们有介绍过线程池的一个基本执行流程<[Java并发编程]面试必备之线程池>以及它的7个核心参数,以及每个参数的作用.以及如何去使用线程池 还留了几个小问题..建议看这篇文 ...

  4. Java 并发编程(二)对象的发布逸出和线程封闭

    对象的发布与逸出 "发布(Publish)"一个对象是指使对象能够在当前作用域之外的代码中使用.可以通过 公有静态变量,非私有方法,构造方法内隐含引用 三种方式. 如果对象构造完成 ...

  5. java并发编程工具类辅助类:CountDownLatch、CyclicBarrier和 Semaphore

    在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier和Semaphore,今天我们就来学习一下这三个辅助类的用法. 以下 ...

  6. java并发编程Future类详解

    作用和举例 future类的作用就是为了调用其他线程完成好后的结果,再返回到当前线程中,如上图举例: 小王自己是主线程,叫外卖等于使用future类,叫好外卖后小王就接着干自己的事去了,当外卖到了的时 ...

  7. Java并发编程—schedule方法和scheduleAtFixedRate方法的区别

    原文作者:一叶丿清风 原文地址:schedule方法和scheduleAtFixedRate方法的区别 schedule方法和scheduleAtFixedRate方法都可以实现任务的延时和不延时执行 ...

  8. Java并发编程:Thread类的使用

    为什么80%的码农都做不了架构师?>>>    Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学 ...

  9. Java 并发编程——Executor框架和线程池原理

    Java 并发编程系列文章 Java 并发基础--线程安全性 Java 并发编程--Callable+Future+FutureTask java 并发编程--Thread 源码重新学习 java并发 ...

最新文章

  1. mysql 主从 网络异常_mysql主从常见异常问题解决
  2. javaserver_如何在JavaServer Pages中使用Salesforce REST API
  3. [渝粤教育] 西南科技大学 英语口语 在线考试复习资料
  4. 前端学习(1988)vue之电商管理系统电商系统之获取商品列表
  5. zabbix入门之添加主机
  6. Openflow协议规范
  7. C++访问WebService(gSoap方式和com组件方式)
  8. Linux下使用USB转串口转换器
  9. 杭州电子科技大学考研计算机科学与技术,杭州电子科技大学考研好考吗
  10. torch cosine_similarity 批量两两计算cos值
  11. 无业务不伸缩之一,云计算有ESS(基础篇)
  12. 具有路线规划功能的外卖平台(Qt,C++,C/S架构,MySql)
  13. 一款炫酷牛逼叼炸天的接口测试神器:【postwoman】(postman女票) 【Apifox】
  14. Java--反编译软件
  15. SpringCloud - 服务注册中心
  16. list.stream.filter常用方式
  17. phpstorm php-cs-fixer 格式化代码
  18. C练题笔记之:Leetcode-654. 最大二叉树
  19. Vscode下jupyter无法显示plot画图,并显示<Figure size 640x480 with 1 Axes>问题解决
  20. ABB机器人ProcCall调用例行程序指令

热门文章

  1. 文件管理(NSfilemanager)
  2. VS2015中DataGridView的DataGridViewComBoboxCell列值无效及数据绑定错误的解决方法
  3. for循环与内置方法详解
  4. 更改列表的默认项标记的颜色、大小等样式的解决办法
  5. 数据结构与算法分析资源总结
  6. Linux启动/停止/重启Mysql数据库的方法
  7. 面向对象 - 1.面向过程/2.面向对象/3.如何使用类/4.__init__方法/5.属性查找/6.补充知识/7.可扩展性高...
  8. mock以及特殊场景下对mock数据的处理
  9. UNION 和UNION ALL的区别
  10. Altera之VIP TPG学习笔记