【多线程】线程是如何启动的?

3,397 阅读3分钟

前言

大家好,今天开始我们来聊一聊多线程。通过这样子的方式来督促自己对知识系统的学习,也希望能给大家一些帮助。

本篇主要知识点

  1. 什么是线程,什么是进程?
  2. 为什么要使用线程?
  3. 创建线程的方式,线程是如何启动的?
  4. 线程的常用方法

很多时候我都习惯使用脑图来记录问题,以及回答问题,这次也继续使用脑图来回答这些问题

下面我们开始稍微深入的探索一下线程的奥秘,go!

Thread类构造方法

1.1 主要构造方法

//构造器
public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    init(group, target, name, stackSize);
}

//使用当前的AccessControlContext初始化线程。
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

主要方法 概念: ThreadGroup:线程组,指的这个现在在哪个组下 Runnable:指需要执行的任务 name:线程名称 stackSize:线程所需要的栈大小 acc:如果为null则调用AccessController.getContext inheritThreadLocals:如果为true,则从构造器中继承局部变量的初始值

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
                  Thread parent = currentThread();
                  ...
     //如果acc为空,则从AccessController.getContext()中获取
     //AccessController类用于访问控制的操作和决定。通过获取上下文的快照
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
       ...
     //如果acc为空,则从AccessController.getContext()中获取
     //AccessController类用于访问控制的操作和决定。通过获取上下文的快照
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
      ...
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
      ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
                  }

1.2 Thread常用方法

//获取当前线程
Thread currentThread()
//让出CPU
void yield();
//使当前线程睡眠一段时间
void sleep(long millis);
//使当前线程停止
void interrupt();
//等待这个线程执行结束
void join(long millis, int nanos);

线程状态转换

看完这,我们再来看下Thread如何启动线程,我们可以跟着线程状态转换来跟踪一下源码。

  1. 第一个状态NEW
 Thread thread = new Thread(() -> {});
    System.out.println(thread.getState()); // 输出 NEW 
  1. 第二个状态RUNNABLE 他包括了RREAY和RUNNING,在进入RUNNABLE,那我们肯定需要调用Thread.start()。

线程调用start开始执行,虚拟机调用run()的方法。

public synchronized void start() {
     //主方法线程和系统组线程创建通过VM
     //状态值为0,设置状态为NEW
     //threadStatus 使用volatile是变量称为可见
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    //将线程加入分组,未启动计数减一
    group.add(this);

    boolean started = false;
    try {
        //{"start0","()V",(void *)&JVM_StartThread))}
        //虚拟机启动一个本地线程,本地线程的创建会调用当前系统创建线程的方法进行创建
        //线程执行时会回调run方法进行业务逻辑的处理
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                //线程启动失败
                group.threadStartFailed(this);
            }
        } 
    }
}

我们仔细看下start0的主要实现

2.1 本地方法

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

2.2 JNI把本地方法注册到jvm中

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

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);
}

2.3 作用是创造一个native_thread本地线程

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

      ...

      native_thread = new JavaThread(&thread_entry, sz);

  ...

  Thread::start(native_thread);

JVM_END

总结

今天我们主要来了解了一下多线程的基础,主要是了解线程的含义,一个进程包含多个线程,一个线程执行一个任务,通过多线程我们来实现并发,以及线程的创建方式主要是Thread和Runnable,最后主要是线程的状态流转,由于篇幅过长,这篇我们先以探索start方法开个头,JNI把本地方法注册到jvm中,创建一个native_thread,真正执行run的就是这个本地线程。

下一篇,我们再继续了解多线程的其他的状态,跟着WAIT源码,看看他的实现。