探究Java多线程中线程的本质

线程的本质

如果想要理解java中的线程,需要学习linux系统中线程的原语,然后自定义MyThread实现Thread的功能更加深入的理解。

linux系统中线程的原语

在linux中,创建一个线程的函数为pthread_create,其定义如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

可以使用man pthread_create查看帮助,下面对四个参数进行简单的说明:

参数名字 参数说明
pthread_t *thread 传出参数,调用之后会传出被创建线程的id
const pthread_attr_t *attr 设置线程的属性,一般传NULL,保持默认属性
void *(*start_routine) (void *) 线程的启动后的主体函数,需要你定义一个函数,然后传函数名即可
void *arg 主体函数的参数,没有可以传null

C语言启动一个线程

代码如下:

# cat threaddemo.c 
#include <pthread.h>
#include <stdio.h>

pthread_t tid;

void* run(void* arg) {
    
    
    printf("I am a thread from c.\n");
}

int main() {
    
    
    
    pthread_create(&tid, NULL, run, NULL);
    sleep(1);
    printf("main end \n");
    return 0;
}

编译:gcc threaddemo.c -o threaddemo.out -pthread

运行结果如下:

# ./threaddemo.out 
I am a thread from c.
main end 

在java中启动一个线程

在java中启动一个线程,最简单的代码如下:

package com.morris.concurrent.thread.os;

public class ThreadDemo {
    
    

    public static void main(String[] args) {
    
    
        Thread t = new Thread(){
    
    
            @Override
            public void run() {
    
    
                System.out.println("I am a thread.");
            }
        };
        t.start();
    }

}

追踪thread.start()方法的源码:

    public synchronized void start() {
    
    

        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
    
    
            start0(); // jni调用本地方法
            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();

可以看到这个方法最核心的就是调用了一个start0方法,而start0方法又是一个native方法,故而如果要搞明白start0我们需要查看Hotspot的源码。

Thread.c中定义了java中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},

jvm.h中声明了JVM_StartThread方法:

/*
 * java.lang.Thread
 */
JNIEXPORT void JNICALL
JVM_StartThread(JNIEnv *env, jobject thread);

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

    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 c++对象,看JavaThread的构造方法
      native_thread = new JavaThread(&thread_entry, sz);

      if (native_thread->osthread() != NULL) {
    
    
        // Note: the current thread is not being used within "prepare".
        native_thread->prepare(jthread);
      }
    }
  }

JavaThread c++对象定义在thread.hpp中:

...c
class JavaThread: public Thread {
...

JavaThread的构造方法实现位于thread.cpp中:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
                       Thread() {
    
    
  initialize();
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  // Create the native thread itself.
  // %note runtime_23
  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);

}

在linux下会调用到os_linux.cpp中

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

  ...

  ThreadState state;

  {
    
    
    pthread_t tid;
    // 调用pthread_create
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);

    ...

  }

总结:Java中创建的线程底层实际上是通过JNI调用操作系统函数pthread_create创建的。

上面的源码跟踪过程可以通过调试OpenJDK源码获得,要调试的代码需要放入编译后的JDK/bin目录中(相当于放入classpath)。

手写Thread

  1. MyThread.java源码
package com.morris.concurrent.thread.os;

public class MyThread {
    
    

    static {
    
    
        System.loadLibrary("MyThreadNative");
    }

    public static void main(String[] args) {
    
    
        MyThread t = new MyThread();
        t.start0();
    }

    private native void start0();
}
  1. 编译和生成头文件:javac -h MyThread.java,这里使用的openjdk11,如果是jdk8,需要使用javac命令编译成class,然后再使用javah命令生成头文件。
# cat MyThread.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MyThread */

#ifndef _Included_MyThread
#define _Included_MyThread
#ifdef __cplusplus
extern "C" {
    
    
#endif
/*
 * Class:     MyThread
 * Method:    start0
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_MyThread_start0
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  1. mythread.c,注意这里面的方法名与Java_MyThread_start0与头文件中声明的保持一致,最开始要引入头文件MyThread.h
#include "MyThread.h" // 引入头文件
#include <pthread.h> 
#include <stdio.h>

pthread_t tid;

void* run(void* arg) {
    
    
    printf("I am a thread from c.\n");
}

Java_MyThread_start0(JNIEnv *env, jobject c1) {
    
     pthread_create(&tid, NULL, run, NULL);
    sleep(1);
    printf("main end \n");
}
  1. 将mythread.c编译成so文件,so文件名必须以lib开头,后面拼接上Java代码中加载的库名称,这样才能被java程序加载到:
# gcc -fPIC -I /usr/local/jdk-11.0.8/include/ -I /usr/local/jdk-11.0.8/include/linux/ -shared -o libMyThreadNative.so mythread.c
  1. 把so文件所在的目录添加到系统变量,不然java还是load不到
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/code/c
  1. 运行(如果有包名的需要包的外面去运行java命令):
# java MyThread
I am a thread from c.
main end 

猜你喜欢

转载自blog.csdn.net/u022812849/article/details/108342672