目录
前面写过一篇文章《Thread类的start()方法创建线程的底层分析》,这次来自定义一个线程,并实现在底层创建内核线程来执行用户代码。
在这之前,先熟悉下Linux中创建内核线程函数pthread_create的用法。

pthread_create函数的第一个参数用来接收新创建的线程ID,该参数的类型是pthread_t。如何查看这个类型的定义?在Linux中查找pthread.h
- [root]# whereis pthread.h
- pthread: /usr/include/pthread.h
打开pthread.h,发现其引入了pthreadtypes.h
- #ifndef _PTHREAD_H
- #define _PTHREAD_H 1
-
- #include
- #include
- #include
- #include
-
- #include
- #include
- #include
打开即可看到pthread_t的定义
typedef unsigned long int pthread_t;
第二个参数用来设置线程属性,可以传空。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
了解了使用方法,编写如下MyThread.c代码。在main方法中创建新线程,然后在main和java_start方法中循环打印。
- #include
- #include
-
- void *java_start(void *arg) {
- while (1)
- {
- usleep(1000000);
- printf("this is java_start method!\n");
- }
-
- }
-
- int main() {
- pthread_t tid;
- pthread_create(&tid, NULL, java_start, NULL);
- while (1)
- {
- usleep(1000000);
- printf("this is main method!\n");
- }
- return 0;
- }
使用如下命令进行编译:
gcc Mythread.c -o MyThread -pthread
运行结果如下:
- [root@192 yangxianzhu]# ./MyThread
- this is java_start method!
- this is main method!
- this is java_start method!
- this is main method!
- this is java_start method!
- this is main method!
- this is java_start method!
- this is main method!
- this is main method!
- this is java_start method!
- this is main method!
- this is java_start method!
至此,就完成了在Linux创建一个新的内核线程。
接下来就开始自定义线程。仿照Thread.java,自定义线程MyThread.java代码如下:
- package org.example;
-
- public class MyThread {
-
- public void start() {
- start0();
- }
-
- private native void start0();
-
- }
其中start0是一个native方法,使用命令javah生成对应的头文件。注意,先进入编译输出目录

然后执行命令:javah org.example.MyThread,生成的头文件名为org_example_MyThread.h,内容如下:
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include
- /* Header for class org_example_MyThread */
-
- #ifndef _Included_org_example_MyThread
- #define _Included_org_example_MyThread
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: org_example_MyThread
- * Method: start0
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_org_example_MyThread_start0
- (JNIEnv *, jobject);
-
- #ifdef __cplusplus
- }
- #endif
- #endif
为了方便后面编译,去除包名。将文件名改成MyThread.h,将里面的方法名JNICALL Java_org_example_MyThread_start0改成JNICALL Java_MyThread_start0。这样,MyThread.java对应的头文件就创建好了。
接下来修改MyThread.c如下:
- #include
- #include
- #include "MyThread.h"
-
- void *java_start(void *arg) {
- while (1)
- {
- usleep(1000000);
- printf("this is java_start method!\n");
- }
-
- }
-
- JNIEXPORT void JNICALL Java_MyThread_start0(JNIEnv *env, jobject obj) {
- pthread_t tid;
- pthread_create(&tid, NULL, java_start, NULL);
- while (1)
- {
- usleep(1000000);
- printf("this is start0 method!\n");
- }
- }
-
- int main() {
- return 0;
- }
首先引入上面生成的头文件MyThread.h,注意这里使用的是#include "MyThread.h",表示该头文件与mythread.c在同一目录下。接着实现Java_MyThread_start0方法,将原先放到main方法里的创建线程的逻辑挪过去。
下面先验证在Java中通过JNI调用mythread.c中的Java_MyThread_start0方法。先将MyThread.c打包成库文件mythread.so,将MyThread.c和MyThread.h拷贝到同一个目录下
- [root@192 yangxianzhu]# pwd
- /home/yangxianzhu
- [root@192 yangxianzhu]# ll
- 总用量 8
- -rw-r--r--. 1 root root 494 12月 4 15:22 MyThread.c
- -rw-r--r--. 1 root root 438 12月 4 14:48 MyThread.h
由于在MyThread.h中引入了jni.h,该文件位于jdk下的include目录。打包命令如下:
- [root@192 yangxianzhu]# gcc -fPIC -I /usr/local/java/jdk1.8.0_341/include -I /usr/local/java/jdk1.8.0_341/include/linux -shared -o MyThread.so MyThread.c
- [root@192 yangxianzhu]# ll
- 总用量 16
- -rw-r--r--. 1 root root 494 12月 4 15:22 MyThread.c
- -rw-r--r--. 1 root root 438 12月 4 14:48 MyThread.h
- -rwxr-xr-x. 1 root root 8184 12月 4 15:32 MyThread.so
MyThread.so就是生成的库文件,接着在MyThread.java中通过System.load引入库文件,然后创建MyThread对象并调用start方法进行测试。
- package org.example;
-
- public class MyThread {
-
- static {
- //加载库文件
- System.load("/home/yangxianzhu/MyThread.so");
- }
-
- public void start() {
- start0();
- }
-
- private native void start0();
-
- public static void main(String[] args) {
- new MyThread().start();
- }
- }
为了方便编译和运行,将MyThread.java中的包路径去除,如下:
- public class MyThread {
-
- static {
- //加载库文件
- System.load("/home/yangxianzhu/MyThread.so");
- }
-
- public void start() {
- start0();
- }
-
- private native void start0();
-
- public static void main(String[] args) {
- new MyThread().start();
- }
- }
然后将其拷贝到Linux中的任意目录,此处放到跟库文件同样的/home/yangxianzhu下
- [root@192 yangxianzhu]# ll
- 总用量 20
- -rw-r--r--. 1 root root 494 12月 4 15:22 MyThread.c
- -rw-r--r--. 1 root root 438 12月 4 14:48 MyThread.h
- -rw-r--r--. 1 root root 311 12月 4 15:37 MyThread.java
- -rwxr-xr-x. 1 root root 8184 12月 4 15:32 MyThread.so
使用javac命令进行编译:
- [root@192 yangxianzhu]# javac MyThread.java
- [root@192 yangxianzhu]# ll
- 总用量 24
- -rw-r--r--. 1 root root 494 12月 4 15:22 MyThread.c
- -rw-r--r--. 1 root root 529 12月 4 15:41 MyThread.class
- -rw-r--r--. 1 root root 438 12月 4 14:48 MyThread.h
- -rw-r--r--. 1 root root 311 12月 4 15:37 MyThread.java
- -rwxr-xr-x. 1 root root 8184 12月 4 15:32 MyThread.so
然后使用java命令运行,结果如下:
- [root@192 yangxianzhu]# java MyThread
- this is java_start method!
- this is start0 method!
- this is java_start method!
- this is start0 method!
- this is java_start method!
- this is start0 method!
- this is java_start method!
- this is start0 method!
至此就完成了在Java中通过JNI调用MyThread.c中的创建内核线程的方法。
下面就是如何在MyThread.c中回调MyThread.java中的方法,在MyThread.java中添加run方法
- public class MyThread {
-
- static {
- //加载库文件
- System.load("/home/yangxianzhu/MyThread.so");
- }
-
- public void start() {
- start0();
- }
-
- private native void start0();
-
- public void run() {
- System.out.println("this is run method");
- }
-
- public static void main(String[] args) {
- new MyThread().start();
- }
- }
修改MyThread.c内容如下:
- #include
- #include
- #include "MyThread.h"
-
- JavaVM *javaVM;
- int status; //线程状态:0|INITIALIZED、1|RUNNABLE
-
- void *java_start(void *arg) {
- JNIEnv *env=NULL;
- (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
- jclass cls;
- jobject obj;
- jmethodID cid;
- jmethodID rid;
- jint ret=0;
- //通过虚拟机找到Java中的类
- cls=(*env)->FindClass(env, "MyThread");
- if (cls==NULL)
- {
- printf("class is not found\n");
- return;
- }
- //如果找到该类,此时通过无参构造函数new出来
- cid=(*env)->GetMethodID(env, cls, "
" , "()V"); - if(cid==NULL) {
- printf("constructor is not found\n");
- }
- //通过构造函数实例化
- obj=(*env)->NewObject(env, cls, cid);
- if(obj==NULL) {
- printf("NewObject is error\n");
- }
- //找到run方法
- rid=(*env)->GetMethodID(env, cls, "run", "()V");
- if (rid==NULL)
- {
- printf("run method id not found\n");
- }
- //直到线程状态不为0|INITIALIZED,才会执行下面的run方法
- while (status == 0)
- {
- usleep(1000000);
- printf("thread state is INITIALIZED\n");
- }
- printf("thread state is RUNNABLE\n");
- //开始调用run方法
- (*env)->CallIntMethod(env, obj, rid, NULL);
- }
-
- JNIEXPORT void JNICALL Java_MyThread_start0(JNIEnv *env, jobject obj) {
- pthread_t tid;
- pthread_create(&tid, NULL, java_start, NULL);
- //休眠5秒后,将线程状态改成1|RUNNABLE
- usleep(5000000);
- status=1;
- }
-
- //JVM启动的时候调用,对javaVM进行赋值
- JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
- javaVM=vm;
- return JNI_VERSION_1_8;
- }
-
- int main() {
- return 0;
- }
重新生成库文件,然后再次编译并运行MyThread,结果如下:
- [root@192 yangxianzhu]# java MyThread
- thread state is INITIALIZED
- thread state is INITIALIZED
- thread state is INITIALIZED
- thread state is INITIALIZED
- thread state is INITIALIZED
- thread state is RUNNABLE
- this is run method
可以看出MyThread.java中的run方法被执行了。
至此就完成了在Java中通过JNI技术调用.C中的代码创建新内核线程,然后在内核线程中回调Java中的方法。