java併發筆記之java線程模型

java併發筆記之java線程模型
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

include

//定義一個變量,接受創建線程後的線程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()到底幹了什麼事才能對比出來。

複製代碼
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

include

//定義一個變量,接受創建線程後的線程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("mainn");
  }
}
複製代碼
三:在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

/ 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

include

//記得導入剛剛編譯的那個.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

include

//記得導入剛剛編譯的那個.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, "", "()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
原文地址https://www.cnblogs.com/yuhangwang/p/11256476.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章