前言
在java中的Thread类中,可以发现,当我们调用了start()方法之后,最终调用的是一个start0()的native方法,这是一个native方法,因为Java是没办法直接使用操作系统的线程的,所以使用了一个native方法去调用了c语言的代码,我们这里利用JNI来实现一个自己的Thread类。具体的JNI操作这里就不多说了,可以查看我关于JNI的其他文章
自定义Thread
一个自定义的Thread的类,其中包含了一个start0()的native方法,最终该方法会回调到Thread的run()方法
public class MyThread{
static{
System.loadLibrary("thread");
}
private Runnable call;
public MyThread(Runnable call){
this.call = call;
}
public void start(){
start0();
}
public void run(){
call.run();
}
public native void start0();
public static void main(String[] args){
MyThread thread = new MyThread(new Task());
thread.start();
try{
while(true){
System.out.println("主线程");
Thread.sleep(500);
}
}catch(Exception e){}
}
}
class Task implements Runnable{
public void run(){
try{
while(true){
System.out.println("其他线程"+Thread.currentThread());
Thread.sleep(500);
}
}catch(Exception e){}
}
}
# 生成class文件
javac MyThread.java
# 生成native对应的头文件
javah MyThread
头文件内容
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MyThread */
#ifndef _Included_MyThread
#define _Included_MyThread
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: MyThread
* Method: start0
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_MyThread_start0
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
实现上面这个头文件声明的函数
#include<MyThread.h>
#include<stdio.h>
#include<pthread.h>
static JavaVM* vm=NULL;
static jobject threadObj;
static jclass clazz;
static int attached;
/**
* 获取JNIEnv*
* 在JNI中JNIEnv在多个线程之间是不能共享的,因为我们要创建一个新的线程
* 所以需要获取一个新的JNIEnv
*/
static JNIEnv* GetEnv(){
int status;
JNIEnv *envnow = NULL;
// 我们创建出来的每一个线程要想访问JVM中的数据结构,必须要先attach到JVM中
// 这里就是判断当前线程是否attach到JVM中,如果已经attach到JVM中了
// 会给envnow设置合适的值
// If the current thread is not attached to the VM, sets *env to NULL, and returns JNI_EDETACHED. If the specified version is not supported, sets *env to NULL, and returns JNI_EVERSION. Otherwise, sets *env to the appropriate interface, and returns JNI_OK.
status = (*vm)->GetEnv(vm,(void **) &envnow, JNI_VERSION_1_8);
if(status < 0)
{
// 将当前线程附加到JVM中
status = (*vm)->AttachCurrentThread(vm,(void**)&envnow, NULL);
if(status < 0)
{
return NULL;
}
attached = 1;
}
return envnow;
}
/**
* 一个线程要想关闭,需要从JVM中detach
*/
static void DetachCurrent(){
if(attached)
{
(*vm)->DetachCurrentThread(vm);
}
}
void* _run(void* args){
JNIEnv* e = GetEnv();
if(!e){
printf("JNIEnv obtain fail");
return NULL;
}
jmethodID method = (*e)->GetMethodID(e,clazz,"run","()V");
(*e)->CallVoidMethod(e,threadObj,method);
// 删除全局引用
DeleteGlobalRef(e, threadObj);
DetachCurrent();
return NULL;
}
JNIEXPORT void JNICALL Java_MyThread_start0(JNIEnv* env,jobject obj){
(*env)->GetJavaVM(env,&vm);
// obj是一个本地引用,本地引用在JNI中不能被多线线程共享,需要创建一个GlobalRef
threadObj = (*env)->NewGlobalRef(env,obj);
jclass c = (*env)->FindClass(env,"MyThread");
if(!c){
printf("class not exists\n");
return ;
}
clazz = (*env)->NewGlobalRef(env,c);
pthread_t id = 10;
// 开启新线程,linux提供的函数
pthread_create(&id,NULL,_run,NULL);
}
其实主要实现就是JNI的那一套,需要注意的点就是本地引用和全局引用。之所以有这样的考虑的主要原因就在于Java中的垃圾回收,为了防止内存泄漏,所以需要严格的遵守JNI的规范,这样垃圾回收才能生效