从Java Thread到OS Thread

tips:这是一篇系列文章,总目录在这里哟~

我们从Java的线程开始,分析一些多年以来的疑问,到底Java的Thread和系统的Thread有什么关系呢?

一、线程的表现形式

1. 从Thread开始

我们从Thread类看起。在Java里,Thread类就用来表示一条线程。
但很明显,Java的Thread只是在应用层面的表象,如何真正创建一条线程,是在JVM中完成的。

public class Thread implements Runnable {
    // ...
        public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        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 then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();
    // ...
}

可以看出,真正的start是调用到start0()的,而start0()是个native方法。
除此之外,有个native的registerNatives方法,备注的是一定要确保registerNatives是最先被调用到的。

    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

2. JVM中的JavaThread

那我们就只能在jvm的源码中看看了:
/src/java.base/share/native/libjava/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},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

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

这其实就是一个函数向量表,注册到env里面去。

可以看到,start0对应到JVM_StartThread函数。
/src/hotspot/prims/jvm.cpp中,可以找到JVM_StartThread函数的实现。

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

  bool throw_illegal_thread_state = false;

  {
    MutexLocker mu(Threads_lock);

    // 从JDK5开始,使用threadStatus来访时re-starting一个已经启动了的线程
    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));
      NOT_LP64(if (size > SIZE_MAX) size = SIZE_MAX;)
      size_t sz = size > 0 ? (size_t) size : 0;
      // 这里创建了JavaThread
      native_thread = new JavaThread(&thread_entry, sz);

      // 有可能由于内存不足,导致没有为Java线程创建osthread,所以需要检查
      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "prepare".
        native_thread->prepare(jthread);
      }
    }
  }

  if (throw_illegal_thread_state) {
    THROW(vmSymbols::java_lang_IllegalThreadStateException());
  }

  // 省略部分代码

  Thread::start(native_thread);

JVM_END

我们先看一下new一个JavaThread的时候,都做了什么?

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : JavaThread() {
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  os::ThreadType thr_type = os::java_thread;
  thr_type = entry_point == &CompilerThread::thread_entry ? os::compiler_thread :
                                                            os::java_thread;
  os::create_thread(this, thr_type, stack_sz);
}

还算比较好理解,JavaThread的构造方法,传入了一个entry_point,这个指的是线程执行函数的入口地址。
在刚才调用new JavaThread的时候实际上传入的是&thread_entry,而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,
                          vmClasses::Thread_klass(),
                          vmSymbols::run_method_name(),
                          vmSymbols::void_method_signature(),
                          THREAD);
}

JavaCalls::call_virtual是用来调用Java方法的,参数值也比较好懂,就是调用哪个对象,那个类、方法、方法签名。
我们跟踪一下vmSymbols::run_method_name(),果不其然:

template(run_method_name, "run")

就是调用到了run方法。这也就是为什么线程被start之后,会自己去执行run方法的原因了。

再看回JavaThread的构造方法本身,实际上是调用到了os::create_thread。而这一步实际上就是调用具体的操作系统提供的调用,去创建线程的过程了。针对于不同的系统,创建的流程也不尽相同。我们就以Linux为例来继续挖一下。

//引入pthread.h,即POSIX Thread接口
# include <pthread.h>

bool os::create_thread(Thread* thread, ThreadType thr_type,
                       size_t req_stack_size) {

  
  OSThread* osthread = new OSThread(NULL, NULL);
  if (osthread == NULL) {
    return false;
  }

  osthread->set_thread_type(thr_type);
  osthread->set_state(ALLOCATED);

  thread->set_osthread(osthread);

  // init thread attributes
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  
  size_t stack_size = os::Posix::get_initial_stack_size(thr_type, req_stack_size);
  int status = pthread_attr_setstacksize(&attr, stack_size);

  ThreadState state;

  {
    pthread_t tid;

    // 创建线程
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);

    
    // 省略其他代码...

  }
  return true;
}

pthread_create有四个参数,它的第三个参数是线程运行函数的起始地址,第四个参数是运行函数参数。
所以,看看thread_native_entry里面是啥:

// Thread start routine for all newly created threads
static void *thread_native_entry(Thread *thread) {
  // 前面省略一大堆初始化和校验代码

  // call one more level start routine
  thread->call_run();

  // ...省略一些后置处理

  return 0;
}

一路跟进去:call_run() --> run() --> thread_main_inner()

void JavaThread::thread_main_inner() {
  // ...省略校验
  if (!this->has_pending_exception() &&
      !java_lang_Thread::is_stillborn(this->threadObj())) {
    // ...省略
      
    // 看到没?调用了entry_point()函数,在之前JavaThread实例化的时候,就已经把entry_point的值赋给了_entry_point,而这里的entry_point()就是将函数get出。
    this->entry_point()(this, this);
  }

  DTRACE_THREAD_PROBE(stop, this);

  // Cleanup is handled in post_run()
}

3. POSIX Thread

pthread_create的时候,又做了哪些事情呢?注意,再往下跟,就已经不再是JVM的代码了,而是转到了操作系统层面了。
pthread_create是遵循POSIX协议标准的创建线程接口。它位于glibc库中,我们拉下代码来分析:

int
__pthread_create_2_1 (newthread, attr, start_routine, arg)
     pthread_t *newthread;
     const pthread_attr_t *attr;
     void *(*start_routine) (void *);
     void *arg;
{
  STACK_VARIABLES;

  const struct pthread_attr *iattr = (struct pthread_attr *) attr;
  if (iattr == NULL)
    /* Is this the best idea?  On NUMA machines this could mean
       accessing far-away memory.  */
    iattr = &default_attr;

  struct pthread *pd = NULL;
  int err = ALLOCATE_STACK (iattr, &pd);
  if (__builtin_expect (err != 0, 0))
    /* Something went wrong.  Maybe a parameter of the attributes is
       invalid or we could not allocate memory.  */
    return err;

  // 省略一大堆的代码……
  /* Pass the descriptor to the caller.  */
  *newthread = (pthread_t) pd;

  /* Remember whether the thread is detached or not.  In case of an
     error we have to free the stacks of non-detached stillborn
     threads.  */
  bool is_detached = IS_DETACHED (pd);

  /* Start the thread. 这句是重点 */
  err = create_thread (pd, iattr, STACK_VARIABLES_ARGS);
  if (err != 0)
    {
      /* Something went wrong.  Free the resources.  */
      if (!is_detached)
	{
	errout:
	  __deallocate_stack (pd);
	}
      return err;
    }

  return 0;
}
versioned_symbol (libpthread, __pthread_create_2_1, pthread_create, GLIBC_2_1);

我们跟进create_thread代码里,发现它的核心是do_clone()方法:

static int
create_thread (struct pthread *pd, const struct pthread_attr *attr,
	       STACK_VARIABLES_PARMS)
{
    // 省略……
		/* Create the thread.  We always create the thread stopped
	     so that it does not get far before we tell the debugger.  */
	  int res = do_clone (pd, attr, clone_flags, start_thread,
			      STACK_VARIABLES_ARGS, 1);
    // 省略……
}

再往里跟:

#ifndef ARCH_CLONE
# define ARCH_CLONE __clone
#endif

if (ARCH_CLONE (fct, STACK_VARIABLES_ARGS, clone_flags,
                pd, &pd->tid, TLS_VALUE, &pd->tid) == -1){
    // 省略……
}

在这里插入图片描述

从这里转入了内核态,执行内核的clone方法。

4. 内核态

在内核态兜兜转转的话,会发现sys_clone()函数又调用到了do_fork()
在这里插入图片描述

可以看出,do_fork()又调用了copy_process()复制进程,但是我们这是线程啊,好像不是进程啊?
其实,Linux内核是不会区分进程还是线程的,他们都用task_struct的结构体来表示。所以,Linux做线程调度和进程调度的逻辑也是一样的。

这样就又串起来了,在定时器的驱动下,不断做着线程切换,我们在上层才有可能拿着线程再去实现各种功能,包括定时器的功能。

5. 总结

Java的Thread类其实就是个普通的对象,它不具备什么魔法。真正创建线程是从执行了start0开始的。
strat0执行后,创建一个JavaThread的对象,而JavaThread对象内部,则是维护了一条操作系统的线程。
我们把在Thread中实现的run方法的入口地址交给了os线程,os线程创建成功后就会开始执行run方法了。
所以,我们可以理解为Thread类是对真正的操作系统线程的包装,它们之间是一一对应的关系。

在定时器的驱动下,调度器控制线程进行切换,我们在上层又可以利用线程的特性再实现定时器出来。

最后,来一个大图,跟进所有代码:
在这里插入图片描述

参考资料

  1. 从源码看为什么start方法才能启动线程,而run不行?,by think123
  2. Linux的进程线程及调度,by 叶余
  3. 线程的创建,by simonl

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值