zoukankan      html  css  js  c++  java
  • java并发笔记一之java线程模型

    警告⚠️:本文耗时很长,先做好心理准备

    需要jni知识才能理解本篇文章(扫盲链接:https://www.jianshu.com/p/87ce6f565d37)

    java当中的线程和操作系统的线程是什么关系?
    猜想: java thread —-对应-—> OS thread
    Linux关于操作系统的线程控制源码:pthread_create()
    Linux命令:man pthread_create

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

    根据man配置的信息可以得出pthread_create会创建一个线程,这个函数是linux系统的函数,可以用C或者C++直接调用,上面信息也告诉程序员这个函数在pthread.h, 这个函数有四个参数:

    然后我们来在linux上启动一个线程的代码:

    创建一个后缀名.c的文件:

    //引入头文件
    #include <pthread.h>
    #include <stdio.h>
    //定义一个变量,接受创建线程后的线程id
    pthread_t pid;
    //定义子线程的主体函数
    void* thread_entity(void* arg)
    {
        while (1)
        {
            usleep(100);
            printf("i am new Thread!
    ");
        }
    }
    //main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
    int main()
    {
        //调用操作系统的函数创建线程,注意四个参数
        pthread_create(&pid,NULL,thread_entity,NULL);
        //usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?为什么需要睡眠?如果不睡眠会出现什么情况
        //让主线程睡眠,目的是为了让子线程执行
        while (1)
        {
            usleep(100);
            printf("main
    ");
        }
    }                       

    运行命令:

    gcc -o thread.out thread.c -pthread
    Thread.out 是thread.c 编译成功之后的文件
    运行:./thread.out 
    输出:
    i am new Thread!
    main
    i am new Thread!
    main
    i am new Thread!
    main
    i am new Thread!
    main
    。。。。。。
    //一直交替执行

    经过以上分析Linux线程创建的过程
    可以试想一下java 的线程模型到底是什么情况?
    分析: java代码里启动一个线程的代码:

    import java.lang.Thread;
    public class ThreadTest {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("i am new Thread!
    ”)
            }
        };
        thread.start();
        }
    }    

    这里启动的线程(start() 方法)和上面我们通过linux的pthread_create()函数启动的线程有什么关系呢?
    只能去可以查看start()的源码了,看看java的start()到底干了什么事才能对比出来。

    start源码
    /**
         * Causes this thread to begin execution; the Java Virtual Machine
         * calls the <code>run</code> method of this thread.
         * <p>
         * The result is that two threads are running concurrently: the
         * current thread (which returns from the call to the
         * <code>start</code> method) and the other thread (which executes its
         * <code>run</code> method).
         * <p>
         * It is never legal to start a thread more than once.
         * In particular, a thread may not be restarted once it has completed
         * execution.
         *
         * @exception  IllegalThreadStateException  if the thread was already
         *               started.
         * @see        #run()
         * @see        #stop()
         */
        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 */
                }
            }
        }
    
        //start0方法是一个native方法
        //native方法:就是一个java调用非java代码的接口,该接口方法的实现由非java语言实现,比如C语言。
        private native void start0();
    
                

    根据Start()源码可以看到这个方法最核心的就是调用了一个start0方法,而start0方法又是一个native方法,故而如果要搞明白start0我们需要查看Hotspot的源码。
    好吧那我们就来看一下Hotspot的源码吧,Hotspot的源码怎么看么??一般直接看openjdk的源码,openjdk的源码如何查看、编译调试?
    Mac 10.14.4 编译openjdk1.9源码 及集成clion动态调试 : https://app.yinxiang.com/fx/b20706bb-ae55-4ec5-a17b-79930e7e67ea
    我们做一个大胆的猜测,java级别的线程其实就是操作系统级别的线程,什么意思呢?
    说白了我们大胆猜想 start()—>start0()—>ptherad_create()
    我们鉴于这个猜想来模拟实现一下:
    一:自己写一个start0()方法来调用一个 native 方法,在native方法中启动一个系统线程
    //java 代码

    public class TestThread {
    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        testThread.start0();
    }
        //native方法
        private native void start0();
    }

    二:然后我们来写一个c程序来启动本地线程:

    #include <pthread.h>
    #include <stdio.h>
    //定义一个变量,接受创建线程后的线程id
    pthread_t pid;
    //定义子线程的主体函数
    void* thread_entity(void* arg)
    {
      while (1)
      {
        usleep(100);
        printf("i am new Thread!
    ");
      }
    }
    //main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
    int main()
    {
      //调用操作系统的函数创建线程,注意四个参数
      pthread_create(&pid,NULL,thread_entity,NULL);
      //usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?为什么需要睡眠?如果不睡眠会出现什么情况
      //让主线程睡眠,目的是为了让子线程执行
      while (1)
      {
        usleep(100);
        printf("main
    ");
      }
    }

    三:在Linux上编译运行C程序:

    编译: gcc -o thread.out thread.c -pthread
    运行: ./thread.out 
    就会出现线程交替执行:
    main
    i am new Thread!
    main
    i am new Thread!
    main
    i am new Thread!
    main
    。。。。。。

    现在的问题就是我们如何通过start0()调用这个c程序,这里就要用到JNI了(JNI自行扫盲)
    Java代码如下:

    public class TestThread {
        static {
            //装载库,保证JVM在启动的时候就会装载,故而一般是也给static
            System.loadLibrary("TestThread");
        }
    
        public static void main(String[] args) {
            TestThread testThread = new TestThread();
            testThread.start0();
      }
    
        private native void start0();
    }            
    在Linux下编译成clas文件:
    编译: javac java1.java 
    生成class文件:java1.class
    在生成 .h 头文件:
    编译: javah TestThread
    生成class文件:TestThread.h
    .h文件分析
    #include <jni.h>
    /* Header for class TestThread */
    #ifndef _Included_TestThread
    #define _Included_TestThread
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
    * Class: TestThread
    * Method: start0
    * Signature: ()V
    */
    //15行代码, Java_com_luban_concurrency_LubanThread_start0方法就是你需要在C程序中定义的方法
    JNIEXPORT void JNICALL Java_TestThread_start0(JNIEnv *, jobject);
        #ifdef __cplusplus
    }
    #endif
    #endif

    然后继续修改.c程序,修改的时候参考.h文件,复制一份.c文件,取名threadNew.c 定义一个Java_com_luban_concurrency_LubanThread_start0 方法在方法中启动一个子线程:

    #include <pthread.h>
    #include <stdio.h>
    //记得导入刚刚编译的那个.h文件
    #include "TestThread.h"
    //定义一个变量,接受创建线程后的线程id
    pthread_t pid;
    //定义子线程的主体函数
    void* thread_entity(void* arg)
    {
        while (1)
        {
            usleep(100);
        printf("i am new Thread!
    ");
        }
    }    
    //这个方法要参考.h文件的15行代码
    JNIEXPOR void JNICALL Java_com_luban_concurrency_LubanThread_start0(JNIEnv *env, jobject c1){
      pthread_create(&pid,NULL,thread_entity,NULL);
      while(1)
      {
        usleep(100);
        printf("I am Java_com_luban_concurrency_LubanThread_start0 
    ");
      }
    }

    解析类,把这个threadNew.c编译成为一个动态链接库,这样在java代码里会被laod到内存libTestThreadNative这个命名需要注意libxx,xx就等于你java那边写的字符串

    gcc ‐fPIC ‐I ${JAVA_HOME}/include ‐I ${JAVA_HOME}/include/linux ‐shared ‐o libTestThreadNative.so threadNew.c
    //需要把这个.so文件加入到path,这样java才能load到:
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}

    直接测试,运行我们自己写的那个java类直接测试看看结果能不能启动线程:
    运行:java java1
    现象:
    main
    I am Java_com_luban_concurrency_LubanThread_start0
    main
    I am Java_com_luban_concurrency_LubanThread_start0
    main
    I am Java_com_luban_concurrency_LubanThread_start0
    main
    。。。。。。
    我们已经通过自己写的一个类,启动了一个线程,但是这个线程函数体是不是java的是C程序的,这个java线程的run方法不同。接下来我们来实现一下这个run:(C来调用java的方法,是jni反调用java方法)
    java的代码里面提供一个run方法:

    public class TestThread {
    static {
    //装载库,保证JVM在启动的时候就会装载,故而一般是也给static
    System.loadLibrary("TestThread");
    }
    
    public static void main(String[] args) {
      TestThread testThread = new TestThread();
      testThread.start0();
    }
    
    //这个run方法,要让C程序员调用到,就完美了
    public void run(){
      System.out.println("I am java Thread !!");
    }
    
    private native void start0();
    }

    C程序:

    #include <pthread.h>
    #include <stdio.h>
    //记得导入刚刚编译的那个.h文件
    #include "TestThread.h"
    //定义一个变量,接受创建线程后的线程id
    pthread_t pid;
    //定义子线程的主体函数
    void* thread_entity(void* arg)
    {
      run();
    }
    //JNIEnv *env 相当于虚拟机
    JNIEXPOR void JNICALL Java_com_luban_concurrency_LubanThread_start0(JNIEnv *env, jobject c1){
      //定一个class 对象
      jclass cls;
      jmethodID cid;
      jmethodID rid;
      //定一个对象
      jobject obj;
      jint ret = 0;
      //通过虚拟机对象找到TestThread java class
      cls = (*env)->FindClass(env,"TestThread");
      if(cls == NULL){
        printf("FindClass Error!
    ")
        return;
      }
      cid = (*env)->GetMethodID(env, cls, "<init>", "()V");
      if (cid == NULL) {
        printf("GetMethodID Error!
    ");
        return;
      }
      //实例化一个对象
      obj = (*env)->NewObject(env, cls, cid);
      if(obj == NULL){
        printf("NewObject Error!
    ")
        return;
      }
      rid = (*env)->GetMethodID(env, cls, "run", "()V");
      ret = (*env)->CallIntMethod(env, obj, rid, Null);
      printf("Finsh call method!
    ")
    }
    int main(){
      return 0;
    }
    然后再走一遍生成.class、.h 、so 然后执行
    jni反调用java编译:
    gcc -o threadNew threadNew.c -I /usr/lib/jvm/java-1.8.0-openjdk/include -I /usr/lib/jvm/java-1.8.0-openjdk/include/linux -L /usr/lib/jvm/java-1.8.0- openjdk/jre/lib/amd64/server -ljvm -pthread
    指定:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}
    显示:
    I am java Thread !!
    Finsh call method!

    至此c调用java的已经完成(只是模拟)(其实C调用java的时候并不是调用jni反射调用的,而是用的C++的一个函数)
    由上可知java thread的调用及反调用:
    调用了一个start0方法,而start0方法又是一个native方法,native方法是由Hotspot提供的,并且调用OS pthread_create()
    证实: java thread —-对应-—> OS thread

    原创不易,转载请标明出处

  • 相关阅读:
    【论文阅读-Embedding】《GloVe: Global Vectors for Word Representation》
    机器学习的问题总结
    预算平滑
    ML基础番外篇-距离度量
    vim配置使用
    强化学习 Note
    强化学习(David Silver)9:探索与利用
    强化学习(David Silver)8:集成学习和计划
    强化学习(David Silver)7:策略梯度算法
    数学基础01-最优化(梯度下降法、牛顿法、拟牛顿法等)
  • 原文地址:https://www.cnblogs.com/yuhangwang/p/11256476.html
Copyright © 2011-2022 走看看