引言
Java 中的 CPU 资源分配对象是 Thread,Go 中的 CPU 资源分配对象是 goroutine。Java Thread 与操作系统的线程是一一对应的关系;goroutine 是 Go实现的用户轻量级线程,通过 GPM 进行管理,与操作系统是 n:m 的关系。
本文旨在通过剖析源码,研究 Java 和 Go 中的线程模型具体的实现和设计思想。
Java Thread
Hotspot JavaThread
在 Hotspot VM 中,Java 中的java.lang.Thread
和操作系统 Thread 是 1:1 的关系,Java 线程会在 start 的时候创建对应的操作系统线程,在Java线程终止的时候回收操作系统线程。
Hotspot 中对与 thread 相关的代码在 jdk/src/hotspot/share/runtime
中,Java11 的JavaThread 是定义在 thread.hpp
和 thread.cpp
中,源码可以看 jdk-11+28 hotspot runtime
// Class hierarchy
// - Thread
// - NamedThread
// - VMThread
// - ConcurrentGCThread
// - WorkerThread
// - GangWorker
// - GCTaskThread
// - JavaThread
// - various subclasses eg CompilerThread, ServiceThread
// - WatcherThread
// Thread
class Thread: public ThreadShadow {
friend class VMStructs;
friend class JVMCIVMStructs;
private:
GCThreadLocalData _gc_data; // GC 中的 ThreadLocal 数据
protected:
void* _real_malloc_address; // 真实分配地址
protected:
OSThread* _osthread; // 和 Thread 关联的 os Thread
}
// JavaThread
class JavaThread: public Thread {
friend class VMStructs;
friend class JVMCIVMStructs;
friend class WhiteBox;
private:
JavaThread* _next; // Threads列表中的下一个 thread
bool _on_thread_list; // 当前 JavaThread 是否在Thread列表中
oop _threadObj; // Java级别的线程对象
}
JavaThread::JavaThread(bool is_attaching_via_jni) :
Thread() {
initialize();
if (is_attaching_via_jni) {
_jni_attach_state = _attaching_via_jni;
} else {
_jni_attach_state = _not_attaching_via_jni;
}
assert(deferred_card_mark().is_empty(), "Default MemRegion ctor");
}
Java11 的 Hotspot 中,会有一个父类 Thread,Thread 中定义 _osThread
用于关联操作系统线程,针对不同父类会有以下子类实现 ( HotSpot Runtime Overview #Thread Management ):
- NamedThread:具有名字的、代表唯一命名实例的线程,定义了
_name
表示线程名- VMThread:负责执行 VM 操作的线程,如 scavenge, garbage_collect 等
- ConcurrentGCThread:负责并发执行垃圾回收的线程
- WorkerThread:指代具有分配工作ID的工作线程,
_id
作为具体的工作ID- GangWorker:字面理解是帮派工作线程
- GCTaskThread:负责GC任务的工作线程
- JavaThread:对应
java.lang.Thread
- CompilerThread:负责运行时,将编译后的字节码转换成机器码 (native thread) 的线程
- ServiceThread:用于低内存检测和JVMTI (虚拟机提供的native接口)
- WatcherThread:负责模拟时钟中断 (simulate timer interrupts)
public:
// Constructor
JavaThread(); // delegating constructor
JavaThread(bool is_attaching_via_jni); // for main thread and JNI attached threads
JavaThread(ThreadFunction entry_point, size_t stack_size = 0);
~JavaThread();
JavaThread::JavaThread(bool is_attaching_via_jni) :
Thread() {
initialize();
if (is_attaching_via_jni) {
_jni_attach_state = _attaching_via_jni;
} else {
_jni_attach_state = _not_attaching_via_jni;
}
assert(deferred_card_mark().is_empty(), "Default MemRegion ctor");
}
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);
}
具体看 JavaThread 的 Constructor,我们可以看到它有两种构造方式,其中 JavaThread(bool is_attaching_via_jni)
仅用于主线程和JNI的连接。故我们所使用的用户线程构造方法是 JavaThread(ThreadFunction entry_point, size_t stack_size = 0)
,这里对 _osthread
进行了创建和绑定,这也决定了在 Hotspot 虚拟机中,Java Thread 与 操作系统线程是一一对应的关系。
Thread JNI
Hotstop 虚拟机中用c++定义了 JavaThread, 对应 JDK 中的 java 定义的 Thread 类,从java调用C++ 使用的是JNI (Java Native Interface)。那么JNI是如何将这两处代码连接起来的?
下面通过源码看看,从创建Java中的Thread对象,到创建VM 中的 JavaThread 是怎样的过程
public class Thread implements Runnable {
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
// native 方法 创建
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
}
这部分可以看到,在Thread.start()
方法中,会调用 start0()
的native方法去创建一个操作系统线程。换句话说,只是 new Thread()
仅仅是在 Java程序中创建了thread对象,并没有对应的系统资源,只有调用了start
方法后才会在虚拟机中创建对应的资源。JDK在jdk/src/java.base/share/native/libjava/Thread.c
中定义了Java中native方法所对应vm 中的 function。
需要注意的是 JNI 的定义是在JDK中用c实现的,但是并未在虚拟机的源码中,这意味着如果不使用Hotspot虚拟机,我只需要在 openjdk 的基础上,在新的虚拟机中实现 JVM_StartThread 方法就行
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield", "()V", (void *)&JVM_Yield},
{"sleep", "(J)V", (void *)&JVM_Sleep},
{"currentThread", "()" THD, (void *)&JVM_CurrentThread},
{"countStackFrames", "()I", (void *)&JVM_CountStackFrames},
{"interrupt0", "()V", (void *)&JVM_Interrupt},
{"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted},
{"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
{"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};
那么 Hotstop 虚拟机 中又是如何实现 JVM_StartThread 的,这里我们需要看 jdk/src/hotspot/share/prims/jvm.cpp
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
bool throw_illegal_thread_state = false;
// We must release the Threads_lock before we can post a jvmti event
// in Thread::start.
{
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;
// 创建 Hotstop 中的 JavaThread,同时创建对应的 _osthread
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);
}
}
}
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
assert(native_thread != NULL, "Starting null thread?");
if (native_thread->osthread() == NULL) {
native_thread->smr_delete();
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
os::native_thread_creation_failed_msg());
}
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
os::native_thread_creation_failed_msg());
}
// 开始执行,执行 os::start_thread
Thread::start(native_thread);
JVM_END
我们总结一下上面的链路
Thread::start() →
native start0() → JNI JNINativeMethod →
JVM_StartThread → Hotstop JavaThread → osthread
线程状态 JMVTI Thread State
JDK中的 Thread.class 和 c++ 代码中的 JavaThread 是一一对应的,线程具体的执行、流转等也是在虚拟机处理的,所以线程状态 (thread status) 在 Hotstop 虚拟机也需要有对应的地方,即JMVTI Thread State,对应的源码是 jvmtiThreadState.hpp
- NEW → JVMTI_THREAD_STATE_ALIVE :
0x0001
- RUNNABLE → JVMTI_THREAD_STATE_RUNNABLE :
0x0004
- BLOCKED → JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER :
0x0400
- WAITING → JVMTI_THREAD_STATE_WAITING_INDEFINITELY :
0x0010
- TIMED_WAITING → JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT :
0x0020
- TERMINATED → JVMTI_THREAD_STATE_TERMINATED :
0x0002
public class Thread implements Runnable {
/*
* Java thread status for tools, default indicates thread 'not yet started'
*/
private volatile int threadStatus;
public enum State {
// Thread state for a thread which has not yet started.
NEW,
// Thread state for a runnable thread.
RUNNABLE,
// Thread state for a thread blocked waiting for a monitor lock.
BLOCKED,
// Thread state for a waiting thread.
WAITING,
// Thread state for a waiting thread with a specified waiting time.
TIMED_WAITING,
// The thread has completed execution.
TERMINATED;
}
}
public class VM {
/* The threadStatus field is set by the VM at state transition
* in the hotspot implementation. Its value is set according to
* the JVM TI specification GetThreadState function.
*/
private static final int JVMTI_THREAD_STATE_ALIVE = 0x0001;
private static final int JVMTI_THREAD_STATE_TERMINATED = 0x0002;
private static final int JVMTI_THREAD_STATE_RUNNABLE = 0x0004;
private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
private static final int JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010;
private static final int JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020;
public static Thread.State toThreadState(int threadStatus) {
if ((threadStatus & JVMTI_THREAD_STATE_RUNNABLE) != 0) {
return RUNNABLE;
} else if ((threadStatus & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER) != 0) {
return BLOCKED;
} else if ((threadStatus & JVMTI_THREAD_STATE_WAITING_INDEFINITELY) != 0) {
return WAITING;
} else if ((threadStatus & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT) != 0) {
return TIMED_WAITING;
} else if ((threadStatus & JVMTI_THREAD_STATE_TERMINATED) != 0) {
return TERMINATED;
} else if ((threadStatus & JVMTI_THREAD_STATE_ALIVE) == 0) {
return NEW;
} else {
return RUNNABLE;
}
}
}
线程池
ThreadPoolExecutor
Java官方库在 JDK 中提供线程池 ThreadPoolExecutor,用于线程复用和线程管理。关于 ThreadPoolExecutor 已经有很多优秀的文章介绍了,这里仅仅强调几个比较重要的参数和线程池的执行流程
ThreadPoolExecutor 线程池创建时有以下3个比较重要的参数:
- corePoolSize:核心线程池数,指的是最小保持活跃线程的数量
- maximumPoolSize:线程池允许创建的最大线程数量,当线程池的任务队列满了之后,可以创建的最大线程数
- workQueue:线程池存放任务的队列,用来存储线程池的所有待执行任务
同时还有一些内部参数至关重要:
- AtomicInteger ctl:主控制参数,记录了线程池的状态和工作线程数量。ctl是一个32位的二进制(包含符号位),去掉符号位有31位,线程池中使用后29位来表示工作线程数,最多可以计数5亿个;用前3位 (包含符号位) 表示线程池的状态。对 ctl 的修改通过 CAS + volatile 保证其线程一致性.
- HashSet workers:正在运行的工作线程,必须在获取到 mainLock 时才能访问,数量和 ctl 中存储的数量是相同的
- ReentrantLock mainLock:用于线程池的锁,对线程池中的 workers 进行访问时,需要先获得此锁,注意这是一个非公平锁
线程池的的执行流程如下:
public class ThreadPoolExecutor extends AbstractExecutorService {
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet workers = new HashSet();
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private final BlockingQueue workQueue;
private volatile long keepAliveTime;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
// 11111...111 2 ^ 29 - 1
private static final int COUNT_MASK = (1