程序猿是高危职业,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: 0Thread-0 say: 1Thread-0 say: 2Thread-0 say: 3Thread-0 say: 4Thread-0 say: 5Thread-0 say: 6Thread-0 say: 7Thread-0 say: 8Thread-0 say: 9
本章节两个案例建议童鞋们自己实践一遍,理解透彻,否则后面的课程跟起来会比较吃力。下个章节会分享如何实现线程池。
各位童鞋在阅读源码或实践的过程中有任何疑惑可留言,我会抽时间一一回复




