暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一、JAVA线程的实现原理

重塑之路 2020-05-14
285

程序猿是高危职业,35岁是一个重要的瓶颈期。在35岁之前你如果没有积蓄过硬的技术实力,或在管理层面踩坑形成自己的管理风格,那在35岁到来之际,在上有老下有小、房贷车贷奶粉等等多重压力下,会过得相当卑微。在这样的危机感下,很多程序猿憋着一口气在往前冲,但很多人不知道冲向何方、很多人冲的不坚定…


其实技术这条路是很好走的,就五个层面:应用层、架构层、编程语言内核层、OS应用层、OS内核层,在今天这样的互联网环境下,你能找到任何你想学习的技术的甚多资料。除了架构层需要外部环境的支撑,其他四个层面你都可以通过自己的努力成为高手。对于JAVA而言,很多人都知道要去研究源码、JVM,但不知从何下手,目前市面上这块的资料还是挺欠缺的,本套课程就是对这块资料的补充。以JNI为切入点,带你深刻理解Java的线程池。不像你之前看过的文章或视频,看到的都是概念,本套课程让这些概念变得看得见摸得着。


本节是系列课程《利用JNI实现线程池》的开篇,将告诉你Java的线程是如何实现的。


完整课程获取方式:关注启明南公众号后回复:jni线程池


文章中贴出的是核心代码,完整代码获取方式:关注本公众号后回复:jni线程池源码


学习本章节需要什么基础


1、能看懂C代码

2、了解Java字节码,你知道什么是属性签名、方法签名

3、本套课程是JNI实战课程,需要你有一定的JNI基础

4、由于Java的线程是基于OS的线程实现的,所以需要你了解Linux下的线程知识


JNI如何抛出异常


由于写代码过程中需要针对变量进程判断、对可能出现的错误进行报错等,而Java的机制是通过抛出异常解决,那JNI如何抛出Java异常呢?


上代码,抛出RuntimeException(如果你看不懂这段代码,去补下C语言基础、JNI基础)

void throwRuntimeException(JNIEnv *jEnv, const char *msg) {
assert(jEnv != NULL);


JNIEnv env = *jEnv;


jclass clazz = env->FindClass(jEnv, "java/lang/RuntimeException");


env->ThrowNew(jEnv, clazz, msg);
}


实现Java伪线程


什么是伪线程?即JNI能运行自己实现的Java线程函数,但还是在主线程中运行


1、线程类

public class MyThread {


public native void run0();


public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " say: " + i);
}
}
}


2、main函数所在类

public class Application {


public static void main(String[] args) {
System.loadLibrary("hello");


new MyThread().run0();
}
}


3、JNI端代码

JNIEXPORT void JNICALL Java_com_qimingnan_jni_threadpool_MyThread_run0
(JNIEnv *jEnv, jobject jobj) {
JNIEnv env = *jEnv;


/* 获取MyThread字节码 */
jclass threadClass = env->GetObjectClass(jEnv, jobj);
if (NULL == threadClass) {
throwRuntimeException(jEnv, "获取线程类字节码失败");


return;
}


/* 获取线程运行函数 */
jmethodID runMethod = env->GetMethodID(jEnv, threadClass, "run", "()V");
if (NULL == runMethod) {
throwRuntimeException(jEnv, "获取线程运行函数失败");


return;
}


/* 执行线程运行函数 */
env->CallVoidMethod(jEnv, jobj, runMethod);
}


实现Java多线程


本案例会调用Linux的线程创建函数pthread_create实现真正意义上的多线程,Java的多线程实现原理与此相当


JNI端代码(Java端代码未做改动)

static JavaVM   *jvm = NULL;
static jclass g_threadClass = NULL;


void* thread_fun(void *arg) {
JNIEnv *jEnv = NULL;
JNIEnv env = NULL;


/* 将当前线程attach到jvm中,传出JNIEnv */
(*jvm)->AttachCurrentThread(jvm, (void **)&jEnv, NULL);


env = *jEnv;


/* 获取线程运行函数 */
jmethodID runMethod = env->GetMethodID(jEnv, g_threadClass, "run", "()V");
if (NULL == runMethod) {
throwRuntimeException(jEnv, "获取线程运行函数失败");


goto return1;
}


/* 实例化线程类 */
jobject threadObj = env->AllocObject(jEnv, g_threadClass);
if (NULL == threadObj) {
throwRuntimeException(jEnv, "实例化线程对象失败");


goto return1;
}


/* 执行线程运行函数 */
env->CallVoidMethod(jEnv, threadObj, runMethod);


return1:
/* 删除注册的全局引用,否则会出现内存泄露 */
env->DeleteGlobalRef(jEnv, g_threadClass);


/* 接触attach,否则会出现线程无法退出或不正常退出、jvm会挂等奇葩情况 */
(*jvm)->DetachCurrentThread(jvm);


return (void *)0;
}


JNIEXPORT void JNICALL Java_com_qimingnan_jni_threadpool_MyThread_run0
(JNIEnv *jEnv, jobject jobj) {
JNIEnv env = *jEnv;
pthread_t tid;


/* 获取MyThread字节码 */
g_threadClass = env->GetObjectClass(jEnv, jobj);
if (NULL == g_threadClass) {
throwRuntimeException(jEnv, "获取线程类字节码失败");


return;
}


/* 注册为全局引用,否则其他线程访问不到 */
env->NewGlobalRef(jEnv, g_threadClass);


/* 获取jvm */
env->GetJavaVM(jEnv, &jvm);


/* 创建OS线程 */
pthread_create(&tid, NULL, thread_fun, NULL);


/* 测试主线程阻塞,Java线程类MyThread是否会运行 */
sleep(5);


/* 等待线程运行结束 */
pthread_join(tid, NULL);
}


编译运行


编译(MAC端)

jcc -dynamiclib -I /Library/Java/JavaVirtualMachines/openjdk-12.0.2.jdk/Contents/Home/include/
com_qimingnan_jni_threadpool_MyThread.c common.c
-o /Users/zhangyu/Library/Java/Extensions/libhello.jnilib


运行结果

Thread-0 say: 0
Thread-0 say: 1
Thread-0 say: 2
Thread-0 say: 3
Thread-0 say: 4
Thread-0 say: 5
Thread-0 say: 6
Thread-0 say: 7
Thread-0 say: 8
Thread-0 say: 9


本章节两个案例建议童鞋们自己实践一遍,理解透彻,否则后面的课程跟起来会比较吃力。下个章节会分享如何实现线程池。


各位童鞋在阅读源码或实践的过程中有任何疑惑可留言,我会抽时间一一回复


文章转载自重塑之路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论