警告: 本文耗时很长, 先做好心理准备
java 当中的线程和操作系统的线程是什么关系?
猜想: java thread -- 对应 --> OS thread
Linux 关于操作系统的线程控制源码: pthread_create()
Linux 命令: man pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
根据 man 配置的信息可以得出 pthread_create 会创建一个线程, 这个函数是 Linux 系统的函数, 可以用 C 或者 C++ 直接调用, 上面信息也告诉程序员这个函数在 pthread.h, 这个函数有四个参数:
然后我们来在 Linux 上启动一个线程的代码:
创建一个后缀名. c 的文件:
- // 引入头文件
- #include <pthread.h>
- #include <stdio.h>
- // 定义一个变量, 接受创建线程后的线程 id
- pthread_t pid;
- // 定义子线程的主体函数
- void* thread_entity(void* arg)
- {
- while (1)
- {
- usleep(100);
- printf("i am new Thread!\n");
- }
- }
- //main 方法, 程序入口, main 和 java 的 main 一样会产生一个进程, 继而产生一个 main 线程
- int main()
- {
- // 调用操作系统的函数创建线程, 注意四个参数
- pthread_create(&pid,NULL,thread_entity,NULL);
- //usleep 是睡眠的意思, 那么这里的睡眠是让谁睡眠呢? 为什么需要睡眠? 如果不睡眠会出现什么情况
- // 让主线程睡眠, 目的是为了让子线程执行
- while (1)
- {
- usleep(100);
- printf("main\n");
- }
- }
运行命令:
gcc -o thread.out thread.c -pthread
Thread.out 是 thread.c 编译成功之后的文件
运行:./thread.out
输出:
- i am new Thread!
- main
- i am new Thread!
- main
- i am new Thread!
- main
- i am new Thread!
- main
- ......
- // 一直交替执行
经过以上分析 Linux 线程创建的过程
可以试想一下 java 的线程模型到底是什么情况?
分析: java 代码里启动一个线程的代码:
- import java.lang.Thread;
- public class ThreadTest {
- public static void main(String[] args) {
- Thread thread = new Thread(){
- @Override
- public void run() {
- System.out.println("i am new Thread!\n")
- }
- };
- thread.start();
- }
- }
这里启动的线程 (start() 方法) 和上面我们通过 Linux 的 pthread_create()函数启动的线程有什么关系呢?
只能去可以查看 start()的源码了, 看看 java 的 start()到底干了什么事才能对比出来.
start 源码
- /**
- * Causes this thread to begin execution; the Java Virtual Machine
- * calls the <code>run</code> method of this thread.
- * <p>
- * The result is that two threads are running concurrently: the
- * current thread (which returns from the call to the
- * <code>start</code> method) and the other thread (which executes its
- * <code>run</code> method).
- * <p>
- * It is never legal to start a thread more than once.
- * In particular, a thread may not be restarted once it has completed
- * execution.
- *
- * @exception IllegalThreadStateException if the thread was already
- * started.
- * @see #run()
- * @see #stop()
- */
- public synchronized void start() {
- /**
- * This method is not invoked for the main method thread or "system"
- * group threads created/set up by the VM. Any new functionality added
- * to this method in the future may have to also be added to the VM.
- *
- * A zero status value corresponds to state "NEW".
- */
- if (threadStatus != 0)
- throw new IllegalThreadStateException();
- /* Notify the group that this thread is about to be started
- * so that it can be added to the group's list of threads
- * and the group's unstarted count can be decremented. */
- group.add(this);
- boolean started = false;
- try {
- start0();
- started = true;
- } finally {
- try {
- if (!started) {
- group.threadStartFailed(this);
- }
- } catch (Throwable ignore) {
- /* do nothing. If start0 threw a Throwable then
- it will be passed up the call stack */
- }
- }
- }
- //start0 方法是一个 native 方法
- //native 方法: 就是一个 java 调用非 java 代码的接口, 该接口方法的实现由非 java 语言实现, 比如 C 语言.
- private native void start0();
根据 Start()源码可以看到这个方法最核心的就是调用了一个 start0 方法, 而 start0 方法又是一个 native 方法, 故而如果要搞明白 start0 我们需要查看 Hotspot 的源码.
好吧那我们就来看一下 Hotspot 的源码吧, Hotspot 的源码怎么看么?? 一般直接看 openjdk 的源码, openjdk 的源码如何查看, 编译调试?
Mac 10.14.4 编译 openjdk1.9 源码 及集成 clion 动态调试 : https://app.yinxiang.com/fx/b20706bb-ae55-4ec5-a17b-79930e7e67ea
我们做一个大胆的猜测, java 级别的线程其实就是操作系统级别的线程, 什么意思呢?
说白了我们大胆猜想 start()->start0()->ptherad_create()
我们鉴于这个猜想来模拟实现一下:
一: 自己写一个 start0()方法来调用一个 native 方法, 在 native 方法中启动一个系统线程
- //java 代码
- public class TestThread {
- public static void main(String[] args) {
- TestThread testThread = new TestThread();
- testThread.start0();
- }
- //native 方法
- private native void start0();
- }
二: 然后我们来写一个 c 程序来启动本地线程:
- #include <pthread.h>
- #include <stdio.h>
- // 定义一个变量, 接受创建线程后的线程 id
- pthread_t pid;
- // 定义子线程的主体函数
- void* thread_entity(void* arg)
- {
- while (1)
- {
- usleep(100);
- printf("i am new Thread!\n");
- }
- }
- //main 方法, 程序入口, main 和 java 的 main 一样会产生一个进程, 继而产生一个 main 线程
- int main()
- {
- // 调用操作系统的函数创建线程, 注意四个参数
- pthread_create(&pid,NULL,thread_entity,NULL);
- //usleep 是睡眠的意思, 那么这里的睡眠是让谁睡眠呢? 为什么需要睡眠? 如果不睡眠会出现什么情况
- // 让主线程睡眠, 目的是为了让子线程执行
- while (1)
- {
- usleep(100);
- printf("main\n");
- }
- }
三: 在 Linux 上编译运行 C 程序:
编译: gcc -o thread.out thread.c -pthread
运行: ./thread.out
就会出现线程交替执行:
- main
- i am new Thread!
- main
- i am new Thread!
- main
- i am new Thread!
- main
- ......
现在的问题就是我们如何通过 start0()调用这个 c 程序, 这里就要用到 JNI 了(JNI 自行扫盲)
Java 代码如下:
- public class TestThread {
- static {
- // 装载库, 保证 JVM 在启动的时候就会装载, 故而一般是也给 static
- System.loadLibrary("TestThread");
- }
- public static void main(String[] args) {
- TestThread testThread = new TestThread();
- testThread.start0();
- }
- private native void start0();
- }
在 Linux 下编译成 clas 文件:
编译: javac java1.java
生成 class 文件: java1.class
在生成 .h 头文件:
编译: javah TestThread
生成 class 文件: TestThread.h
.h 文件分析
- #include <jni.h>
- /* Header for class TestThread */
- #ifndef _Included_TestThread
- #define _Included_TestThread
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: TestThread
- * Method: start0
- * Signature: ()V
- */
- //15 行代码, Java_com_luban_concurrency_LubanThread_start0 方法就是你需要在 C 程序中定义的方法
- JNIEXPORT void JNICALL Java_TestThread_start0(JNIEnv *, jobject);
- #ifdef __cplusplus
- }
- #endif
- #endif
然后继续修改. c 程序, 修改的时候参考. h 文件, 复制一份. c 文件, 取名 threadNew.c 定义一个 Java_com_luban_concurrency_LubanThread_start0 方法在方法中启动一个子线程:
- #include <pthread.h>
- #include <stdio.h>
- // 记得导入刚刚编译的那个. h 文件
- #include "TestThread.h"
- // 定义一个变量, 接受创建线程后的线程 id
- pthread_t pid;
- // 定义子线程的主体函数
- void* thread_entity(void* arg)
- {
- while (1)
- {
- usleep(100);
- printf("i am new Thread!\n");
- }
- }
- // 这个方法要参考. h 文件的 15 行代码
- JNIEXPOR void JNICALL Java_com_luban_concurrency_LubanThread_start0(JNIEnv *env, jobject c1){
- pthread_create(&pid,NULL,thread_entity,NULL);
- while(1)
- {
- usleep(100);
- printf("I am Java_com_luban_concurrency_LubanThread_start0 \n");
- }
- }
解析类, 把这个 threadNew.c 编译成为一个动态链接库, 这样在 java 代码里会被 laod 到内存 libTestThreadNative 这个命名需要注意 libxx,xx 就等于你 java 那边写的字符串
- gcc -fPIC -I ${
- JAVA_HOME
- }/include -I ${
- JAVA_HOME
- }/include/Linux -shared -o libTestThreadNative.so threadNew.c
- // 需要把这个. so 文件加入到 path, 这样 java 才能 load 到:
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{
- libLubanThreadNative.so
- }
直接测试, 运行我们自己写的那个 java 类直接测试看看结果能不能启动线程:
运行: java java1
现象:
- main
- I am Java_com_luban_concurrency_LubanThread_start0
- main
- I am Java_com_luban_concurrency_LubanThread_start0
- main
- I am Java_com_luban_concurrency_LubanThread_start0
- main
- ......
我们已经通过自己写的一个类, 启动了一个线程, 但是这个线程函数体是不是 java 的是 C 程序的, 这个 java 线程的 run 方法不同. 接下来我们来实现一下这个 run:(C 来调用 java 的方法, 是 jni 反调用 java 方法)
java 的代码里面提供一个 run 方法:
- public class TestThread {
- static {
- // 装载库, 保证 JVM 在启动的时候就会装载, 故而一般是也给 static
- System.loadLibrary("TestThread");
- }
- public static void main(String[] args) {
- TestThread testThread = new TestThread();
- testThread.start0();
- }
- // 这个 run 方法, 要让 C 程序员调用到, 就完美了
- public void run(){
- System.out.println("I am java Thread !!");
- }
- private native void start0();
- }
C 程序:
- #include <pthread.h>
- #include <stdio.h>
- // 记得导入刚刚编译的那个. h 文件
- #include "TestThread.h"
- // 定义一个变量, 接受创建线程后的线程 id
- pthread_t pid;
- // 定义子线程的主体函数
- void* thread_entity(void* arg)
- {
- run();
- }
- //JNIEnv *env 相当于虚拟机
- JNIEXPOR void JNICALL Java_com_luban_concurrency_LubanThread_start0(JNIEnv *env, jobject c1){
- // 定一个 class 对象
- jclass cls;
- jmethodID cid;
- jmethodID rid;
- // 定一个对象
- jobject obj;
- jint ret = 0;
- // 通过虚拟机对象找到 TestThread java class
- cls = (*env)->FindClass(env,"TestThread");
- if(cls == NULL){
- printf("FindClass Error!\n")
- return;
- }
- cid = (*env)->GetMethodID(env, cls, "<init>", "()V");
- if (cid == NULL) {
- printf("GetMethodID Error!\n");
- return;
- }
- // 实例化一个对象
- obj = (*env)->NewObject(env, cls, cid);
- if(obj == NULL){
- printf("NewObject Error!\n")
- return;
- }
- rid = (*env)->GetMethodID(env, cls, "run", "()V");
- ret = (*env)->CallIntMethod(env, obj, rid, Null);
- printf("Finsh call method!\n")
- }
- int main(){
- return 0;
- }
然后再走一遍生成. class,.h ,so 然后执行
jni 反调用 java 编译:
gcc -o threadNew threadNew.c -I /usr/lib/jvm/java-1.8.0-openjdk/include -I /usr/lib/jvm/java-1.8.0-openjdk/include/Linux -L /usr/lib/jvm/java-1.8.0- openjdk/jre/lib/amd64/server -ljvm -pthread
指定: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libLubanThreadNative.so}
显示:
- I am java Thread !!
- Finsh call method!
至此 c 调用 java 的已经完成(只是模拟)(其实 C 调用 java 的时候并不是调用 jni 反射调用的, 而是用的 C++ 的一个函数)
由上可知 java thread 的调用及反调用:
调用了一个 start0 方法, 而 start0 方法又是一个 native 方法, native 方法是由 Hotspot 提供的, 并且调用 OS pthread_create()
证实: java thread -- 对应 --> OS thread
来源: https://www.cnblogs.com/yuhangwang/p/11256476.html