警告⚠️:本文耗时很长,先作好心理准备java
须要jni知识才能理解本篇文章(扫盲连接:https://www.jianshu.com/p/87ce6f565d37)linux
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, 这个函数有四个参数:app
而后咱们来在linux上启动一个线程的代码:jvm
建立一个后缀名.c的文件:ide
//引入头文件 #include <pthread.h> #include <stdio.h> //定义一个变量,接受建立线程后的线程id pthread_t pid; //定义子线程的主体函数 void* thread_entity(void* arg) { while (1) { usleep(100); printf("i am new Thread!\n"); } } //main方法,程序入口,main和java的main同样会产生一个进程,继而产生一个main线程 int main() { //调用操做系统的函数建立线程,注意四个参数 pthread_create(&pid,NULL,thread_entity,NULL); //usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?为何须要睡眠?若是不睡眠会出现什么状况 //让主线程睡眠,目的是为了让子线程执行 while (1) { usleep(100); printf("main\n"); } }
运行命令:函数
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!\n”) } }; thread.start(); } }
这里启动的线程(start() 方法)和上面咱们经过linux的pthread_create()函数启动的线程有什么关系呢?
只能去能够查看start()的源码了,看看java的start()到底干了什么事才能对比出来。this
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 代码spa
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!\n"); } } //main方法,程序入口,main和java的main同样会产生一个进程,继而产生一个main线程 int main() { //调用操做系统的函数建立线程,注意四个参数 pthread_create(&pid,NULL,thread_entity,NULL); //usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?为何须要睡眠?若是不睡眠会出现什么状况 //让主线程睡眠,目的是为了让子线程执行 while (1) { usleep(100); printf("main\n"); } }
三:在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!\n"); } } //这个方法要参考.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 \n"); } }
解析类,把这个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!\n") return; } cid = (*env)->GetMethodID(env, cls, "<init>", "()V"); if (cid == NULL) { printf("GetMethodID Error!\n"); return; } //实例化一个对象 obj = (*env)->NewObject(env, cls, cid); if(obj == NULL){ printf("NewObject Error!\n") return; } rid = (*env)->GetMethodID(env, cls, "run", "()V"); ret = (*env)->CallIntMethod(env, obj, rid, Null); printf("Finsh call method!\n") } 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
原创不易,转载请标明出处