线程的本质
如果想要理解java中的线程,需要学习linux系统中线程的原语,然后自定义MyThread实现Thread的功能更加深入的理解。
linux系统中线程的原语
在linux中,创建一个线程的函数为pthread_create,其定义如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
可以使用man pthread_create
查看帮助,下面对四个参数进行简单的说明:
参数名字 | 参数说明 |
pthread_t *thread | 传出参数,调用之后会传出被创建线程的id |
const pthread_attr_t *attr | 设置线程的属性,一般传NULL,保持默认属性 |
void *(*start_routine) (void *) | 线程的启动后的主体函数,需要你定义一个函数,然后传函数名即可 |
void *arg | 主体函数的参数,没有可以传null |
C语言启动一个线程
代码如下:
# cat threaddemo.c
#include <pthread.h>
#include <stdio.h>
pthread_t tid;
void* run(void* arg) {
printf("I am a thread from c.\n");
}
int main() {
pthread_create(&tid, NULL, run, NULL);
sleep(1);
printf("main end \n");
return 0;
}
编译:gcc threaddemo.c -o threaddemo.out -pthread
。
运行结果如下:
# ./threaddemo.out
I am a thread from c.
main end
在java中启动一个线程
在java中启动一个线程,最简单的代码如下:
package com.morris.concurrent.thread.os;
public class ThreadDemo {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("I am a thread.");
}
};
t.start();
}
}
追踪thread.start()方法的源码:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0(); // jni调用本地方法
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 */
}
}
}
private native void start0();
可以看到这个方法最核心的就是调用了一个start0方法,而start0方法又是一个native方法,故而如果要搞明白start0我们需要查看Hotspot的源码。
Thread.c中定义了java中Thread类中的本地方法与c语言方法的映射:
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
jvm.h中声明了JVM_StartThread方法:
/*
* java.lang.Thread
*/
JNIEXPORT void JNICALL
JVM_StartThread(JNIEnv *env, jobject thread);
jvm.cpp中JVM_StartThread的实现:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
bool throw_illegal_thread_state = false;
{
MutexLocker mu(Threads_lock);
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
NOT_LP64(if (size > SIZE_MAX) size = SIZE_MAX;)
size_t sz = size > 0 ? (size_t) size : 0;
// 创建了一个JavaThread c++对象,看JavaThread的构造方法
native_thread = new JavaThread(&thread_entry, sz);
if (native_thread->osthread() != NULL) {
// Note: the current thread is not being used within "prepare".
native_thread->prepare(jthread);
}
}
}
JavaThread c++对象定义在thread.hpp中:
...c
class JavaThread: public Thread {
...
JavaThread的构造方法实现位于thread.cpp中:
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread() {
initialize();
_jni_attach_state = _not_attaching_via_jni;
set_entry_point(entry_point);
// Create the native thread itself.
// %note runtime_23
os::ThreadType thr_type = os::java_thread;
thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
os::java_thread;
// 调用具体系统的函数创建线程
os::create_thread(this, thr_type, stack_sz);
}
在linux下会调用到os_linux.cpp中
bool os::create_thread(Thread* thread, ThreadType thr_type,
size_t req_stack_size) {
assert(thread->osthread() == NULL, "caller responsible");
...
ThreadState state;
{
pthread_t tid;
// 调用pthread_create
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);
...
}
总结:Java中创建的线程底层实际上是通过JNI调用操作系统函数pthread_create创建的。
上面的源码跟踪过程可以通过调试OpenJDK源码获得,要调试的代码需要放入编译后的JDK/bin目录中(相当于放入classpath)。
手写Thread
- MyThread.java源码
package com.morris.concurrent.thread.os;
public class MyThread {
static {
System.loadLibrary("MyThreadNative");
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start0();
}
private native void start0();
}
- 编译和生成头文件:
javac -h MyThread.java
,这里使用的openjdk11,如果是jdk8,需要使用javac命令编译成class,然后再使用javah命令生成头文件。
# cat MyThread.h
/* 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
- mythread.c,注意这里面的方法名与Java_MyThread_start0与头文件中声明的保持一致,最开始要引入头文件
MyThread.h
:
#include "MyThread.h" // 引入头文件
#include <pthread.h>
#include <stdio.h>
pthread_t tid;
void* run(void* arg) {
printf("I am a thread from c.\n");
}
Java_MyThread_start0(JNIEnv *env, jobject c1) { pthread_create(&tid, NULL, run, NULL);
sleep(1);
printf("main end \n");
}
- 将mythread.c编译成so文件,so文件名必须以lib开头,后面拼接上Java代码中加载的库名称,这样才能被java程序加载到:
# gcc -fPIC -I /usr/local/jdk-11.0.8/include/ -I /usr/local/jdk-11.0.8/include/linux/ -shared -o libMyThreadNative.so mythread.c
- 把so文件所在的目录添加到系统变量,不然java还是load不到
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/code/c
- 运行(如果有包名的需要包的外面去运行java命令):
# java MyThread
I am a thread from c.
main end