警告⚠️:本文耗時很長,先作好心理準備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
原創不易,轉載請標明出處