Java中Thread是怎么执行的?
我们知道java中执行一个线程分为两步:创建Thread和启动start
第一步:创建线程对象。继承Thread对象重写run方法,或者实现Runnable接口run方法并通过Thread的构造方法创建Thread对象,
第二步:调用Thread的start方法启动线程
按照上面两步操作后,就可以开启线程来执行任务了,如下:
package cn.ckeen.threadtest;import java.util.concurrent.atomic.AtomicInteger;/ * @description:线程创建方法 * @author: CKeen * @date: 2022/5/16 10:05 上午 */public class MainThread extends Thread { public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) { // 方式一: 继承Thread类,重写run方法 Thread thread = new MainThread(); thread.start(); // 实现runnable接口 Thread thread2 = new Thread(new Runnable() { @Override public void run() { doWork(); } }); thread2.start(); } @Override public void run() { doWork(); } static void doWork(){ while(count.get() < 1000){ int countVal = count.getAndIncrement(); System.out.println("Hello World! count:" + String.valueOf(countVal)); try { Thread.sleep(1000L); }catch (Exception ex){ ex.printStackTrace(); } } }}
看到这里我们一定会很好奇,Thread创建和执行的过程是怎么样的呢?
下面我们就来一步一步跟踪Thread的创建和执行过程:
第一步,我们看下Thread对象的创建,我们通过Thread类跳转到JDK源码可以看到有以下几个构造函数
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0);}public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0);}Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);}public Thread(ThreadGroup group, Runnable target) { init(group, target, "Thread-" + nextThreadNum(), 0);}public Thread(String name) { init(null, null, name, 0);}public Thread(ThreadGroup group, String name) { init(group, null, name, 0);}public Thread(Runnable target, String name) { init(null, target, name, 0);}public Thread(ThreadGroup group, Runnable target, String name) { init(group, target, name, 0);}public Thread(ThreadGroup group, Runnable target, String name, long stackSize) { init(group, target, name, stackSize);}
这里实际干活的是Thread的init方法,我们继续跟到init
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { if (security != null) { g = security.getThreadGroup(); } if (g == null) { g = parent.getThreadGroup(); } } g.checkAccess(); if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize; tid = nextThreadID();}
init方法实际只是初始化了Thread的一个参数,比如thread的分组group,target设置,堆栈大小,tid等,到这里Thread对象就已经创建完成
第二步,我们继续看一下Thread对象的启动方法start:
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } }}private native void start0();
跳转到start方法,可以看到start方法调用了start0,而start0方法是个native方法,实际干活的虚拟机对外提供的native的方法了。
为了进一步搞清楚Thread是怎么启动的,必须得知道start0方法都干了些什么,所以我们要跟踪到JVM的实现部分源码。这里我们基于openjdk版本jdk8-b120版本的进行研究,具体的源码可以自己去github的openjdk拉取。
下面我们具体来分析一下JDK对start0实现:
1. 打开源码目录,首先我们可以看到代码目录结构
我们只分析源码部分,不实际编译实际JDK的代码,所以我们主要关注两个以下两个目录:
- jni函数映射目录jdk/src/share/native/java/,该目录下有jdk的native函数到JVM函数的映射定义。
- jvm实现目录hotspot/src/share/vm, 等一会我们会涉及到该目录下的prims目录和runtime目录。
2. 我们找到Thread类的中native函数映射文件:thread.c(源码详细路径:jdk/share/native/java/lang/)
// jdk/share/native/java/lang/thread.cstatic 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},};
这里我们看到start0的native方法映射到了JVM的JVM_StartThread方法,我们继续看一下该方法的实现
2. 我们查到JVM_StartThread方法,可以在jvm.h的头文件中找到JVM_StartThread方法的定义,如下:
// hotspot的虚拟机实现:hotspot/src/share/vm/prims/jvm.hJNIEXPORT void JNICALLJVM_StartThread(JNIEnv *env, jobject thread);
进一步我们可以找到jvm.cpp代码的对JVM_StartThread方法的实现,如下:
// hotspot/src/share/vm/prims/jvm.cppJVM_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)); native_thread = new JavaThread(&thread_entry, sz); if (native_thread->osthread() != NULL) { 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) { delete native_thread; if (JvmtiExport::should_post_resource_exhausted()) { JvmtiExport::post_resource_exhausted( JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS, "unable to create new native thread"); } THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),"unable to create new native thread"); } Thread::start(native_thread);JVM_END
这里除了做一些线程对象的初始化工作外,主要有两步操作:
1. native_thread = new JavaThread(&thread_entry, sz);创建一个JavaThread对象
2. 使用Thread::start(native_thread) 启动线程
而JavaThread的对象,实际位于hotspot的runtime的下thread.cpp文件,该文件主要定义了JVM中使用的Thread对象,根据其头文件描述,我们可以看到他主要有如下划分:
而JavaThread是hotspot的JVM实现的,对应于java用户定义的Thread对象的实际对象。jvm.cpp中的调用的JavaThread的构造函数定义如下:
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : Thread()#if INCLUDE_ALL_GCS , _satb_mark_queue(&_satb_mark_queue_set), _dirty_card_queue(&_dirty_card_queue_set)#endif // INCLUDE_ALL_GCS{ if (TraceThreadEvents) { tty->print_cr("creating thread %p", this); } 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); _safepoint_visible = false;}
源码中我们可以看到在创建JavaThread的对象的时候,使用os::create_thread方法创建了一个系统线程,该线程对应于系统的内核线程,最终实际干活的为该线程。我们经常说java线程和实际执行的内核线程配比为1:1,应该就是对应于每个JavaThread的都创建了一个系统线程的与其对应。
JavaThread的对象创建完成,也创建了一个os的thread预期对应,接着调用了Thread::start(native_thread)的方法,启动线程,我们找到Thread的::start方法如下:
void Thread::start(Thread* thread) { trace("start", thread); if (!DisableStartThread) { if (thread->is_Java_thread()) { java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),java_lang_Thread::RUNNABLE); } os::start_thread(thread); }}
这里Thread的start方法直接调用os::start_thread启动了系统线程,到此java的线程就可以等待系统的调度执行了。
总结一下实际执行过程: