JNI实现山寨版的Thread

前言

在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的规范,这样垃圾回收才能生效

JNI官方手册

发布了162 篇原创文章 · 获赞 44 · 访问量 8821

猜你喜欢

转载自blog.csdn.net/P19777/article/details/104059519
今日推荐