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


    说在前面:

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

    版本信息:
    jdk版本:jdk8u40

    源码剖析:

    1. public
    2. class Thread implements Runnable {}

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

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

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

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

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

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

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

    1. public Thread(Runnable target) {
    2. init(null, target, "Thread-" + nextThreadNum(), 0);
    3. }
    4. private void init(ThreadGroup g, Runnable target, String name,
    5. long stackSize) {
    6. init(g, target, name, stackSize, null, true);
    7. }
    8. private void init(ThreadGroup g, Runnable target, String name,
    9. long stackSize, AccessControlContext acc,
    10. boolean inheritThreadLocals) {
    11. if (name == null) {
    12. throw new NullPointerException("name cannot be null");
    13. }
    14. this.name = name;
    15. Thread parent = currentThread();
    16. SecurityManager security = System.getSecurityManager();
    17. if (g == null) {
    18. if (security != null) {
    19. g = security.getThreadGroup();
    20. }
    21. if (g == null) {
    22. g = parent.getThreadGroup();
    23. }
    24. }
    25. g.checkAccess();
    26. if (security != null) {
    27. if (isCCLOverridden(getClass())) {
    28. security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
    29. }
    30. }
    31. g.addUnstarted();
    32. this.group = g;
    33. this.daemon = parent.isDaemon();
    34. this.priority = parent.getPriority();
    35. if (security == null || isCCLOverridden(parent.getClass()))
    36. this.contextClassLoader = parent.getContextClassLoader();
    37. else
    38. this.contextClassLoader = parent.contextClassLoader;
    39. this.inheritedAccessControlContext =
    40. acc != null ? acc : AccessController.getContext();
    41. this.target = target;
    42. setPriority(priority);
    43. if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    44. this.inheritableThreadLocals =
    45. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    46. this.stackSize = stackSize;
    47. tid = nextThreadID();
    48. }

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

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

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

    1. public synchronized void start() {
    2. // 此线程的状态如果不是创建状态就直接抛出非法逻辑异常。
    3. if (threadStatus != 0)
    4. throw new IllegalThreadStateException();
    5. // 添加到对应的线程组中。
    6. group.add(this);
    7. boolean started = false;
    8. try {
    9. // Native方法,此方法会创建底层的线程,并且启动线程,并且执行线程的执行体(Runnable的run方法)
    10. start0();
    11. started = true; // 正常启动完毕。
    12. } finally {
    13. try {
    14. if (!started) {
    15. group.threadStartFailed(this);
    16. }
    17. } catch (Throwable ignore) {
    18. }
    19. }
    20. }
    21. private native void start0();

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

    1. {"start0", "()V", (void *)&JVM_StartThread}
    2. JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
    3. JVMWrapper("JVM_StartThread");
    4. JavaThread *native_thread = NULL;
    5. bool throw_illegal_thread_state = false;
    6. {
    7. MutexLocker mu(Threads_lock);
    8. // 获取到Java开发者设置的栈大小,这个参数一般不会设置
    9. jlong size =
    10. java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
    11. size_t sz = size > 0 ? (size_t) size : 0;
    12. // 创建底层的线程。
    13. // 当创建好线程后,会回调thread_entry此方法。
    14. native_thread = new JavaThread(&thread_entry, sz);
    15. if (native_thread->osthread() != NULL) {
    16. // 初始化线程,比如是否是守护线程,线程优先级。以及线程数量的统计。
    17. native_thread->prepare(jthread);
    18. }
    19. }
    20. // 设置成运行中状态
    21. Thread::start(native_thread);
    22. JVM_END

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

    1. JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
    2. Thread() // 调用父类构造方法
    3. {
    4. // 初始化参数
    5. initialize();
    6. _jni_attach_state = _not_attaching_via_jni;
    7. // 设置执行入口,当底层线程创建好以后,会去回调此方法
    8. set_entry_point(entry_point);
    9. // 设置此线程的类型,当前线程是java_thread类型
    10. os::ThreadType thr_type = os::java_thread;
    11. thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
    12. os::java_thread;
    13. // 真正的线程只有操作系统才有权利去创建,所以这里调用操作系统类库去创建。
    14. os::create_thread(this, thr_type, stack_sz);
    15. _safepoint_visible = false;
    16. }
    17. bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
    18. assert(thread->osthread() == NULL, "caller responsible");
    19. OSThread* osthread = new OSThread(NULL, NULL);
    20. osthread->set_thread_type(thr_type);
    21. osthread->set_state(ALLOCATED);
    22. thread->set_osthread(osthread);
    23. pthread_attr_t attr;
    24. pthread_attr_init(&attr);
    25. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    26. ………… // 省略一部分设置参数代码
    27. // glibc guard page
    28. pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));
    29. ThreadState state;
    30. {
    31. pthread_t tid;
    32. // java_start 作为统一的入口,后续再根据thread类型做分发(多态)。
    33. int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
    34. pthread_attr_destroy(&attr);
    35. osthread->set_pthread_id(tid);
    36. // Wait until child thread is either initialized or aborted
    37. {
    38. Monitor* sync_with_child = osthread->startThread_lock();
    39. MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
    40. while ((state = osthread->get_state()) == ALLOCATED) {
    41. sync_with_child->wait(Mutex::_no_safepoint_check_flag);
    42. }
    43. }
    44. if (lock) {
    45. os::Linux::createThread_lock()->unlock();
    46. }
    47. }
    48. return true;
    49. }

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

    1. // pthread线程回调执行点。
    2. static void *java_start(Thread *thread) {
    3. ………… // 省略大部分的设置参数和状态值的代码,方便观看核心源码
    4. // 这里是区分JVM层面抽象的不同线程的不同执行点(多态)
    5. // 而这里thread对象是JavaThread
    6. thread->run();
    7. return 0;
    8. }
    9. void JavaThread::run() {
    10. ………… // 省略大部分的设置参数和状态值的代码,方便观看核心源码
    11. // 执行设置的入口
    12. thread_main_inner();
    13. }

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

    1. void JavaThread::thread_main_inner() {
    2. // 不存在异常
    3. if (!this->has_pending_exception() &&
    4. !java_lang_Thread::is_stillborn(this->threadObj())) {
    5. HandleMark hm(this);
    6. // 回调设置的入口
    7. this->entry_point()(this, this);
    8. }
    9. DTRACE_THREAD_PROBE(stop, this);
    10. // 执行到这里代表线程执行结束了,需要做释放工作。
    11. this->exit(false);
    12. delete this;
    13. }

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

    1. static void thread_entry(JavaThread* thread, TRAPS) {
    2. HandleMark hm(THREAD);
    3. Handle obj(THREAD, thread->threadObj());
    4. JavaValue result(T_VOID);
    5. JavaCalls::call_virtual(&result,
    6. obj,
    7. KlassHandle(THREAD, SystemDictionary::Thread_klass()),
    8. vmSymbols::run_method_name(),
    9. vmSymbols::void_method_signature(),
    10. THREAD);
    11. }

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

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

    上面的流程对于不懂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源码层面,所以部分代码很多读者看不懂。但是代码是科学,他并不是神学,要论证真实性,必须要深入到源码层面。

  • 相关阅读:
    Java线程与锁-1
    百面算法工程师 | 分类和聚类
    ZingGrid JS 1.4.0 Crack
    MongoDB的「Linux」安装及基本使用
    华为云服务
    react嵌套路由
    与字节、小米、移动云等企业一起揭秘 RocketMQ 实践之道
    简析Acrel-1000安科瑞变电站综合自动化系统选型与应用
    codePen按钮样式学习
    第七章·原型模式
  • 原文地址:https://blog.csdn.net/qq_43799161/article/details/133680001