關(guān)于操作系統(tǒng)的線程,linux操作系統(tǒng)的線程控制原語
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
根據(jù)man配置的信息可以得出pthread_create會創(chuàng)建一個線程,這個函數(shù)是linux系統(tǒng)的函數(shù),可以用C或者C++直接調(diào)用,上面信息也告訴程序員這個函數(shù)在pthread.h, 這個函數(shù)有四個參數(shù)
在linux上啟動一個線程的代碼:
#include <pthread.h>//頭文件
#include <stdio.h>
pthread_t pid;//定義一個變量,接受創(chuàng)建線程后的線程id
//定義線程的主體函數(shù)
void* thread_entity(void* arg)
{
printf("i am new Thread!");
}
//main方法,程序入口,main和java的main一樣會產(chǎn)生一個進程,繼而產(chǎn)生一個main線程
int main()
{
//調(diào)用操作系統(tǒng)的函數(shù)創(chuàng)建線程,注意四個參數(shù)
pthread_create(&pid,NULL,thread_entity,NULL);
//usleep是睡眠的意思,那么這里的睡眠是讓誰睡眠呢?
//為什么需要睡眠?如果不睡眠會出現(xiàn)什么情況
usleep(100);
printf("main\n");
}
假設有了上面知識的鋪墊,那么可以試想一下java的線程模型到底是什么情況呢?
在java代碼里啟動一個線程的代碼
public class Example4Start {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("i am new Thread!");
}
};
thread.start();
}
}
這里啟動的線程和上面我們通過linux的pthread_create函數(shù)啟動的線程有什么關(guān)系呢?
只能去可以查看start()的源碼了,看看java的start()到底干了什么事才能對比出來.
start源碼的部分截圖,可以看到這個方法最核心的就是調(diào)用了一個start0方法,而start0方法又是一個native方法,故而如果要搞明白start0我們需要查看Hotspot的源碼,好吧那我們就來看一下Hotspot的源碼吧,Hotspot的源碼怎么看么?一般直接看openjdk的源碼,openjdk的源碼如何查看、編譯調(diào)試?openjdk的編譯我們后面會討論,在沒有openjdk的情況下,我們做一個大膽的猜測,java級別的線程其實就是操作系統(tǒng)級別的線程,什么意思呢?說白了我們大膽猜想 start-start0-ptherad_create
我們鑒于這個猜想來模擬實現(xiàn)一下。
如何來模擬實現(xiàn)呢?
public class LubanThread {
public static void main(String[] args) {
LubanThread lubanThread =new LubanThread();
lubanThread.start0();
}
private native void start0();
}
這里我們讓自己寫的start0調(diào)用一個本地方法,在本地方法里面去啟動一個系統(tǒng)線程,好吧我們寫一個c程序來啟動本地線程
#include <pthread.h>
#include <stdio.h>
pthread_t pid;
void* thread_entity(void* arg)
{
while(1){
usleep(100);
printf("I am new Thread\n");
}
}
int main()
{
pthread_create(&pid,NULL,thread_entity,NULL);
while(1){
usleep(100);
printf("I am main\n");
}
}
在linux上編譯、運行上述C程序
gcc thread.c -o thread.out -pthread
./thread.out
結(jié)果是兩個線程一直在交替執(zhí)行,得到我們預期的結(jié)果?,F(xiàn)在的問題就是我們?nèi)绾瓮ㄟ^start0調(diào)用這個c程序,這里就要用到JNI了,如果你預習了epoll的課(當然epoll我沒有講完,看看大家的接受程度再決定要不要講完)那么JNI調(diào)用就應該懂了
好吧你要是實在不懂,再說一遍
java代碼如下:
package com.luban.concurrency;
public class LubanThread {
static {
System.loadLibrary( "LubanThreadNative" );
}
public static void main(String[] args) {
LubanThread lubanThread =new LubanThread();
lubanThread.start0();
}
private native void start0();
}
裝載庫,保證JVM在啟動的時候就會裝載,故而一般是也給static
System.loadLibrary( "LubanThreadNative" );
編譯成class文件
生成.h頭文件
javah packageName.className
需要注意的運行javah命令得在包外面和編譯不一樣,編譯運行javac得在包當中
生成的.h文件,最好把他移動到和class文件同級目錄吧
繼而開始編寫C程序,要上面那個thread.c程序上做一點修改,這里我復制一份出來修改,修改的時候需要參考那個這個.h文件,一下是.h文件的內(nèi)容
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_luban_concurrency_LubanThread */
#ifndef _Included_com_luban_concurrency_LubanThread
#define _Included_com_luban_concurrency_LubanThread
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_luban_concurrency_LubanThread
* Method: start0
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_luban_concurrency_LubanThread_start0
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
上面第十五代碼中的Java_com_luban_concurrency_LubanThread_start0方法就是你需要在C程序中定義的方法。
首先復制一份thread.c
修改threadNew.c,定義一個方法Java_com_luban_concurrency_LubanThread_start0,在方法中啟動一個子線程,代碼如下
#include <pthread.h>
#include <stdio.h>
#include "com_luban_concurrency_LubanThread.h"http://記得導入剛剛編譯的那個.h文件
pthread_t pid;
void* thread_entity(void* arg)
{
while(1){
usleep(100);
printf("I am new Thread\n");
}
}
//這個方法要參考.h文件的15行代碼,這里的參數(shù)得注意,你寫死就行,不用明白為什么
JNIEXPORT 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 main\n");
}
}
int main()
{
return 0;
}
解析類,把這個threadNew.c編譯成為一個動態(tài)鏈接庫,這樣在java代碼里會被laod到內(nèi)存
libLubanThreadNative這個命名需要注意libxx,xx就等于你java那邊寫的字符串
gcc -fPIC -I /usr/lib/jvm/java-1.8.0-openjdk/include -I /usr/lib/jvm/java-1.8.0-openjdk/include/linux -shared -o libLubanThreadNative.so threadNew.c
做完這一系列事情之后需要把這個.so文件加入到path,這樣java才能load到
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}所在的路徑
萬事俱備,直接測試,運行我們自己寫的那個java類直接測試看看結(jié)果能不能啟動線程
回車
牛逼!我們已經(jīng)通過自己寫的一個類,啟動了一個線程,但是這個線程函數(shù)體是不是java的是C程序的,這個java線程的run方法不同。接下來我們來實現(xiàn)一下這個run
首先在java的代碼里面提供一個run方法
package com.luban.concurrency;
public class LubanThread {
static {
System.loadLibrary( "LubanThreadNative" );
}
public static void main(String[] args) {
LubanThread lubanThread =new LubanThread();
lubanThread.start0();
}
//這個run方法,要讓C程序員調(diào)用到,就完美了
public void run(){
System.out.println("I am java Thread !!");
}
private native void start0();
}
所以現(xiàn)在的問題就是讓C程序當中的thread_entity方法調(diào)用到java當中的run方法?思路同樣是jni反調(diào)用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
LD_LIBRARY_PATH=/usr/lib/jvm/java-1.8.0-openjdk/jre/lib/amd64/server ./a