沉淀、分享、成长,让⾃⼰和他⼈都能有所收获!
⼀、前⾔
有句话:正因为你优秀,所以难以卓越!
刚开始听这句话还在上学,既不卓越、也不优秀,甚⾄可能还有点笨!但突然从某次爬到班级的前⼏名后,开始喜欢上了这种感觉,原来前⾯的风景是如此灿烂!
优秀和卓越差的不是⼀个等级,当你感觉⾃⼰优秀后,还能保持空瓶的⼼态开始,才能逐步的像卓越迈进,并漫漫长!
是不⼩时候更容易学会更多的知识,但越⼤越笨了!⼈可能很容易被⾃⼰的年纪⼤了,当成长者。却很少能保持⼀个低姿态谦卑的⼼态,不断的学习。所以最后,放不下⾃⼰,也拾不起能⼒。
喜欢⼀句话,蓝是天的颜⾊、红是⽕的象征,我不学⼤海抄袭天的蓝、也不学晚霞模拟⽕的红。我就是我,⽣命是我的、命运是我的。健⾝也是你的、学习也是你的,只要你有⼀个好⼼态,⾃然会⾛到前⾯卓越那⾥!
⼆、⾯试题
谢飞机,⼩记! 码德,年轻⼈写代码好猖狂,不遵守规范还喷我,你要耗⼦尾汁!谢飞机骂骂咧咧的下班后,找⾯试官聊⼼得。谢飞机:我感觉天天就像活在粪堆,代码都是乱糟糟,我有⼼⽆⼒!⾯试官:怎么,想跳槽了?
谢飞机:想去写代码有规范的公司,想提升!
⾯试官:嗯!确实,有些⼤公司的代码质量要好⼀些。但是你也要⾃⾝能⼒强的。谢飞机:是的,我⼀直在努⼒学习!准备跑路!
⾯试官:那我顺便考你个题,看看你进⼤⼚的⼏率⼤不。嗯... Java 线程如何启动的?谢飞机:如何启动的?start 启动的!⾯试官:还有吗?谢飞机:嗯...,没了!
⾯试官:嗯,可能会与不会这⼀个题并不会让你代码有多⽜、有多好,但是你的技术栈深度和⼴度,决定你的编程职业⽣涯是否有⼀条康庄⼤道。还是要多努⼒!
三、线程启动分析
new Thread(() -> { // todo}).start();
咳咳,Java 的线程创建和启动⾮常简单,但如果问⼀个线程是怎么启动起来的往往并不清楚,甚⾄不知道为什么启动时是调⽤start(),⽽不是调⽤run()⽅法呢?
那么,为了让⼤家有⼀个更直观的认知,我们先站在上帝视⾓。把这段 Java 的线程代码,到 JDK ⽅法使⽤,以及 JVM 的相应处理过程,展⽰给⼤家,以⽅便我们后续逐步分析。
以上,就是⼀个线程启动的整体过程分析,会涉及到如下知识点:
线程的启动会涉及到本地⽅法(JNI)的调⽤,也就是那部分 C++ 编写的代码。JVM 的实现中会有不同操作系统对线程的统⼀处理,⽐如:Win、Linux、Unix。
线程的启动会涉及到线程的⽣命周期状态(RUNNABLE),以及唤醒操作,所以最终会有回调操作。也就是调⽤我们的 run() ⽅法接下来,我们就开始逐步分析每⼀步源码的执⾏内容,从⽽了解线程启动过程。
四、线程启动过程
1. Thread start UML 图
如图 19-2 是线程的启动过程时序图,整体的链路较长,会涉及到 JVM 的操作。核⼼源码如下:Thread.c:jvm.cpp:thread.cpp:os.cpp:
os_linux.cpp:os_windows.cpp:vmSymbols.hpp:
2. Java 层⾯ Thread 启动
2.1 start() ⽅法new Thread(() -> { // todo}).start();
// JDK 源码
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) {} }}
线程启动⽅法 start(),在它的⽅法英⽂注释中已经把核⼼内容描述出来。Causes this thread to begin execution; the Java Virtual Machinecalls the run method of this thread. 这段话的意思是:由 JVM 调⽤此线程的 run ⽅法,使线程开始执⾏。其实这就是⼀个 JVM 的回调过程,下⽂源码分析中会讲到另外 start() 是⼀个 synchronized ⽅法,但为了避免多次调⽤,在⽅法中会由线程状态判断。threadStatus != 0。group.add(this),是把当前线程加⼊到线程组,ThreadGroup。
start0(),是⼀个本地⽅法,通过 JNI ⽅式调⽤执⾏。这⼀步的操作才是启动线程的核⼼步骤。2.2 start0() 本地⽅法// 本地⽅法 start0
private native void start0();
// 注册本地⽅法
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing registerNatives(); } // ...} start0(),是⼀个本地⽅法,⽤于启动线程。 registerNatives(),这个⽅法是⽤于注册线程执⾏过程中需要的⼀些本地⽅法,⽐如:start0、isAlive、yield、sleep、interrupt0等。registerNatives,本地⽅法定义在 Thread.c 中,以下是定义的核⼼源码:static JNINativeMethod methods[] = { {\"start0\ {\"stop0\ {\"isAlive\ {\"suspend0\ {\"resume0\ {\"setPriority0\ {\"yield\ {\"sleep\ {\"currentThread\ {\"interrupt0\ {\"holdsLock\ {\"getThreads\ {\"dumpThreads\ {\"setNativeName\}; 源码: 从定义中可以看到,start0 ⽅法会执⾏ &JVM_StartThread ⽅法,最终由 JVM 层⾯启动线程。 3. JVM 创建线程 3.1 JVM_StartThread JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) JVMWrapper(\"JVM_StartThread\"); JavaThread *native_thread = NULL; // 创建线程 native_thread = new JavaThread(&thread_entry, sz); // 启动线程 Thread::start(native_thread); JVM_END 这部分代码⽐较多,但核⼼内容主要是创建线程和启动线程,另外 &thread_entry 也是⼀个⽅法,如下:thread_entry,线程⼊⼝ static void thread_entry(JavaThread* thread, TRAPS) { HandleMark hm(THREAD); Handle obj(THREAD, thread->threadObj()); JavaValue result(T_VOID); JavaCalls::call_virtual(&result, obj, KlassHandle(THREAD, SystemDictionary::Thread_klass()), vmSymbols::run_method_name(), vmSymbols::void_method_signature(), THREAD);} 重点,在创建线程引⼊这个线程⼊⼝的⽅法时,thread_entry 中包括了 Java 的回调函数 JavaCalls::call_virtual。这个回调函数会由 JVM 调⽤。 vmSymbols::run_method_name(),就是那个被回调的⽅法,源码如下:#define VM_SYMBOLS_DO(template, do_alias)template(run_method_name, \"run\") 这个 run 就是我们的 Java 程序中会被调⽤的 run ⽅法。接下来我们继续按照代码执⾏链路,寻找到这个被回调的⽅法在什么时候调⽤的。3.2 JavaThread native_thread = new JavaThread(&thread_entry, sz);接下来,我们继续看 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\ } 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);} ThreadFunction entry_point,就是我们上⾯的 thread_entry ⽅法。size_t stack_sz,表⽰进程中已有的线程个数。 这两个参数,都会传递给 os::create_thread ⽅法,⽤于创建线程使⽤。3.3 os::create_thread源码: os_linux.cpp:os_windows.cpp: 众所周知,JVM 是个啥!,所以它的 OS 服务实现,Liunx 还有 Windows 等,都会实现线程的创建逻辑。这有点像适配器模式os_linux -> os::create_thread bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) { assert(thread->osthread() == NULL, \"caller responsible\"); // Allocate the OSThread object OSThread* osthread = new OSThread(NULL, NULL); // Initial state is ALLOCATED but not INITIALIZED osthread->set_state(ALLOCATED); pthread_t tid; int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread); return true;} osthread->set_state(ALLOCATED),初始化已分配的状态,但此时并没有初始化。 pthread_create,是类Unix操作系统(Unix、Linux、Mac OS X等)的创建线程的函数。java_start,重点关注类,是实际创建线程的⽅法。3.4 java_start static void *java_start(Thread *thread) { // 线程ID int pid = os::current_process_id(); // 设置线程 ThreadLocalStorage::set_thread(thread); // 设置线程状态:INITIALIZED 初始化完成 osthread->set_state(INITIALIZED); // 唤醒所有线程 sync->notify_all(); // 循环,初始化状态,则⼀致等待 wait while (osthread->get_state() == INITIALIZED) { sync->wait(Mutex::_no_safepoint_check_flag); } // 等待唤醒后,执⾏ run ⽅法 thread->run(); return 0;} JVM 设置线程状态,INITIALIZED 初始化完成。sync->notify_all(),唤醒所有线程。 osthread->get_state() == INITIALIZED,while 循环等待 thread->run(),是等待线程唤醒后,也就是状态变更后,才能执⾏到。这在我们的线程执⾏UML图中,也有所体现4. JVM 启动线程 JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) JVMWrapper(\"JVM_StartThread\"); JavaThread *native_thread = NULL; // 创建线程 native_thread = new JavaThread(&thread_entry, sz); // 启动线程 Thread::start(native_thread); JVM_END JVM_StartThread 中有两步,创建(new JavaThread)、启动(Thread::start)。创建的过程聊完了,接下来我们聊启动。4.1 Thread::start void Thread::start(Thread* thread) { trace(\"start\ if (!DisableStartThread) { if (thread->is_Java_thread()) { java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(), java_lang_Thread::RUNNABLE); } // 不同的 OS 会有不同的启动代码逻辑 os::start_thread(thread); }} 如果没有禁⽤线程 DisableStartThread 并且是 Java 线程 thread->is_Java_thread(),那么设置线程状态为 RUNNABLE。os::start_thread(thread),调⽤线程启动⽅法。不同的 OS 会有不同的启动代码逻辑4.2 os::start_thread(thread) void os::start_thread(Thread* thread) { // guard suspend/resume MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag); OSThread* osthread = thread->osthread(); osthread->set_state(RUNNABLE); pd_start_thread(thread);} osthread->set_state(RUNNABLE),设置线程状态 RUNNABLE pd_start_thread(thread),启动线程,这个就由各个 OS 实现类,实现各⾃系统的启动⽅法了。⽐如,windows系统和Linux系统的代码是完全不同的。4.3 pd_start_thread(thread) void os::pd_start_thread(Thread* thread) { OSThread * osthread = thread->osthread(); assert(osthread->get_state() != INITIALIZED, \"just checking\"); Monitor* sync_with_child = osthread->startThread_lock(); MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag); sync_with_child->notify(); } 这部分代码 notify() 最关键,它可以唤醒线程。 线程唤醒后,3.4 中的 thread->run(); 就可以继续执⾏了。 5. JVM 线程回调 5.1 thread->run()[JavaThread::run()] // The first routine called by a new Java threadvoid JavaThread::run() { // ... 初始化线程操作 thread_main_inner();} os_linux.cpp 类中的 java_start ⾥的 thread->run(),最终调⽤的就是 thread.cpp 的 JavaThread::run() ⽅法。这部分还需要继续往下看,thread_main_inner(); ⽅法。5.2 thread_main_inner void JavaThread::thread_main_inner() { if (!this->has_pending_exception() && !java_lang_Thread::is_stillborn(this->threadObj())) { { ResourceMark rm(this); this->set_native_thread_name(this->get_thread_name()); } HandleMark hm(this); this->entry_point()(this, this); } DTRACE_THREAD_PROBE(stop, this); this->exit(false); delete this;} 这⾥有你熟悉的设置的线程名称,this->set_native_thread_name(this->get_thread_name())。this->entry_point(),实际调⽤的就是 3.1 中的 thread_entry ⽅法。 thread_entry,⽅法最终会调⽤到 JavaCalls::call_virtual ⾥的vmSymbols::run_method_name()。也就是 run() ⽅法,⾄此线程启动完成。终于串回来了!五、总结 线程的启动过程涉及到了 JVM 的参与,所以如果没有认真了解过,确实很难从⼀个本地⽅法了解的如此透彻。 整个源码分析可以结合着代码调⽤UML时序图进⾏学习,基本核⼼过程包括:Java 创建线程和启动、调⽤本地⽅法 start0()、JVM 中JVM_StartThread 的创建和启动、设置线程状态等待被唤醒、根据不同的OS启动线程并唤醒、最后回调 run() ⽅法启动 Java 线程。 有时候可能只是⼀步很简单的⽅法,也会有它的深⼊之处,当真的懂了以后,就不⽤死记硬背。如果需要获得以上⾼清⼤图,可以添加⼩傅哥微信(fustack),备注:Thread⼤图六、系列推荐 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- 69lv.com 版权所有 湘ICP备2023021910号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务