模拟java线程
java中的线程与操作系统中的线程有什么区别
首先我们来查看 lunix 中的线程,通过lunix中提供的线程原语 man pthread_create 命令查看
执行上面的方法。我们可以看到
根据man配置的信息可以得出 pthread_create 会创建一个线程,这个函数是linux系统的函数,可以用C或者C++直接调用,上面信息也告诉程序员这个函数在pthread.h中,需要导入
这个函数 有四个参数
pthread_t *thread | 传出参数,调用之后会传出被创建线程的id | 定义 pthread_t pid; 继而取地址 &pid |
---|---|---|
const pthread_attr_t *attr | 传出参数,调用之后会传出被创 建线程的id线程属性,关于线程属性是linux 的知识 | 在学习pthread_create函数的时 候一般穿NULL,保持默认属性 |
void *(*start_routine) (void *) | 线程的启动后的主体函数 相当于java当中的run | 需要你定义一个函数,然后传函 数名即可 |
void *arg | 主体函数的参数 | 如果没有可以传NULL |
我们通过C来调用lunix的 pthread_create
方法
#include <pthread.h>//头文件 ,C库
#include <stdio.h>
pthread_t pid;//定义一个变量,接受创建线程后的线程id
//定义线程的主体函数
void* thread_entity(void* arg) {
while(1){
usleep(100); //usleep是睡眠的意思,那么这里的睡眠是让主线程睡眠
printf("i am new Thread!\n");
}
}
//main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
int main(){
//调用操作系统的函数创建线程,注意四个参数 ,调用的是上面的函数
pthread_create(&pid,NULL,thread_entity,NULL);
//防止创建完线程之后主线程关闭,这里用while循环来一直循环
while(1){
usleep(100); //usleep是睡眠的意思,那么这里的睡眠是让主线程睡眠
printf("main\n");
}
return 0;
}
编译C文件 gcc -o thread thread.c -pthread
-o 表示编译之后的文件 ,如果不指定,就会默认的编译为.out文件
编译之后会产出一个 thread文件
我们来执行这个 thread 文件 在该目录下执行
./thread
可以看出我们调用了,操作系统的线程
那我们回到java中,那java中线程和操作系统中的线程有什么区别,我们可以通过查看java的线程的start方法源码
可以得出看到java调用start0方法,而start0()方法是native修饰的本地方法
因此我们猜测出,java的线程调用C文件,C文件调用操作系统函数
java 调用c ,c 调用 Hotspot , Hotspot调用 os函数。
这里有一个问题?
为什么C不直接调用os函数,而是调用Hotspot的?
因为我们java提供的有很多api,如果我们这些api对应到这c文件中,那我们又会有很多c文件,和本地方法。
所有当我们调用c的时候,其实在Hotspot中也创建了一个对象,javaThread
,这个对象调用os函数, 抽象出来。
下面我们来验证run方法是pthread_create的第三个参数,思路如下:
1 , 通过java启动一个线程(os线程) — 他的主体函数是C程序提供的函数 ( jni调用 )
2 , 验证run ----- 通过java启动线程,回调我的run方法
验证:
提供一个java文件:
public class LubanThread {
static {
// 装载c文件到内存中,提供一个c 文件,编译成这个样子就行了
// c文件提供start0 方法,当我们调用start0 的时候就回去找到对应的c文件中的方法,在c文件中调用 lunix的线程方法
System.loadLibrary( "LubanThreadNative" );
}
public static void main(String[] args) {
LubanThread lubanThread =new LubanThread();
lubanThread.start0();
}
public void run(){
System.out.println("I am new Thread!!");
}
private native void start0();
}
通过javac 编译此文件
编译完成之后,要生成 .h
文件,在c文件中 引入改文件
上面的报错是因为的java的版本高于 1.7,所以就用不了
这个就是编译后的c 文件, 我们的start0 方法被编译成了 Java_LubanThread_start0
此处非常重要
这个时候我们来编写c文件
#include <pthread.h>//头文件 ,C库
#include <stdio.h>
#include "RuhrThread.h" // java生成的文件
pthread_t pid;//定义一个变量,接受创建线程后的线程id
//定义线程的主体函数
void* thread_entity(void* arg) {
while(1){
usleep(100); //usleep是睡眠的意思,那么这里的睡眠是让主线程睡眠
printf("i am new Thread!\n");
}
}
//main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
//int main(){
//调用操作系统的函数创建线程,注意四个参数 ,调用的是上面的函数
//pthread_create(&pid,NULL,thread_entity,NULL);
//防止创建完线程之后主线程关闭,这里用while循环来一直循环
//while(1){
//usleep(100); //usleep是睡眠的意思,那么这里的睡眠是让主线程睡眠
//printf("main\n");
//}
//return 0;
//}
//这个方法要参考.h文件的15行代码,这里的参数得注意,你写死就行,不用明白为什么
JNIEXPORT void JNICALL Java_LubanThread_start0(JNIEnv *env, jobject c1){
//调用操作系统的函数创建线程,注意四个参数 ,调用的是上面的函数
pthread_create(&pid,NULL,thread_entity,NULL);
//防止创建完线程之后主线程关闭,这里用while循环来一直循环
while(1){
usleep(100); //usleep是睡眠的意思,那么这里的睡眠是让主线程睡眠
printf("main\n");
}
}
int main() {
return 0;
}
解析类,把这个thread.c编译成为一个动态链接库,这样在java代码里会被laod到内存 libLubanThreadNative这个命名需要注意libxx,xx就等于你java那边写的字符串
运行下面的命令生成 .so文件
gcc -fPIC -I /usr/lib/jvm/java-openjdk/include -I /usr/lib/jvm/java-openjdk/include/linux/ -shared -o libLubanThreadNative.so thread.c -pthread
此处要注意,lunix安装的是openjdk! openjdk! 不是jdk
做完这一系列事情之后需要把这个.so文件加入到path,这样java才能load到
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}
所在的路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:
/home/thread
运行java文件. 我们来见证奇迹 java RuhrThread
我们完成了java通过jni调用c 语言文件,下一步我们来验证c反向调用java
我们来编写c文件:
#include <pthread.h>//头文件 ,C库
#include <stdio.h>
#include "RuhrThread.h"
pthread_t pid;//定义一个变量,接受创建线程后的线程id
//定义线程的主体函数
void* thread_entity(void* arg) {
while(1){
usleep(100); //usleep是睡眠的意思,那么这里的睡眠是让主线程睡眠
printf("i am new Thread!\n");
}
}
//main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
//int main(){
//调用操作系统的函数创建线程,注意四个参数 ,调用的是上面的函数
//pthread_create(&pid,NULL,thread_entity,NULL);
//防止创建完线程之后主线程关闭,这里用while循环来一直循环
//while(1){
//usleep(100); //usleep是睡眠的意思,那么这里的睡眠是让主线程睡眠
//printf("main\n");
//}
//return 0;
//}
//这个方法要参考.h文件的15行代码,这里的参数得注意,你写死就行,不用明白为什么
//JNIEXPORT void JNICALL Java_RuhrThread_start0(JNIEnv *env, jobject c1){
//调用操作系统的函数创建线程,注意四个参数 ,调用的是上面的函数
// pthread_create(&pid,NULL,thread_entity,NULL);
//防止创建完线程之后主线程关闭,这里用while循环来一直循环
//while(1){
//usleep(100); //usleep是睡眠的意思,那么这里的睡眠是让主线程睡眠
//printf("main\n");
//}
//}
// 这次我们来调用java中的run方法
JNIEXPORT void JNICALL Java_RuhrThread_start0(JNIEnv *env, jobject c1){
// 在Java中我们如何调用方法的呢?
// class -- new Object --method
// 在c语言中我们通过虚拟机来获取到对象 JNIEnv *env 代表虚拟机
jclass cls; //类
jobject obj;
jmethodID cid; //构造方法
jmethodID rid;
jint ret=0;
cls=(*env)->FindClass(env,"RuhrThread"); // 通过虚拟机找到类
// 通过类new对象
if(cls==NULL){
printf("cindClass error!\n");
return;
}
// 调用构造方法
cid=(*env)->GetMethodID(env,cls,"<init>","()V");
if(cid ==NULL){
printf("query constructor error!\n");
return;
}
// 创建对象
obj=(*env)-> NewObject(env,cls,cid);
if(obj==NULL){
printf("NewObjcct crror!\n");
}
// 获取run方法
rid=(*env)->GetMethodID(env,cls,"run","()V");
//执行run方法
ret=(*env)->CallIntMethod(env,obj,rid,NULL);
printf("finsh call method!\n");
}
int main() {
return 0;
}
// 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
// gcc -o thread thread.c -I /usr/lib/jvm/java-openjdk/include -I /usr/lib/jvm/java-openjdk/include/linux -I /usr/lib/jvm/java-openjdk/jre/lib/amd64/server -ljvm -pthread
//gcc -fPIC -I /usr/lib/jvm/java-openjdk/include -I /usr/lib/jvm/java-openjdk/include/linux/ -shared -o libLubanThreadNative.so thread.c -pthread
//export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:
编写完后,需要把编写的c进行编译,并且加入到jdk中去,运行一下命令
gcc -o thread thread.c -I /usr/lib/jvm/java-openjdk/include -I /usr/lib/jvm/java-openjdk/include/linux -I /usr/lib/jvm/java-openjdk/jre/lib/amd64/server -ljvm -pthread
在编译一下我们的java文件,命令如下:
gcc -fPIC -I /usr/lib/jvm/java-openjdk/include -I /usr/lib/jvm/java-openjdk/include/linux/ -shared -o libLubanThreadNative.so thread.c -pthread
再将java文件加入到jdk中去
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/thread
最后执行: java RuhrThread
完成java调用C,C在调用java