thread start()方法源码解析
Posted diweikang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了thread start()方法源码解析相关的知识,希望对你有一定的参考价值。
无论用哪种方式实现多线程,最终究其根源都是在调用Thread类的start()方法。而此时就有一个疑问了,若要实现多线程需要覆写其run()方法,而为什么要使用start()启动线程而不直接调用run()方法?
- 1.首先来看一下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 */
}
}
}
从整个方法上的注释看,start()使得该线程开始执行,Java虚拟机调用这个线程的run()方法,结果是两个线程会并发的运行
:当前线程(调用start()方法的"主线程")与另一个线程(执行其run方法)
- 2.3.start()之后的代码的核心就是其中调用的start0()方法,下面看看start0()的定义:
private native void start0();
start0 是一个本地的方法,start0()这个方法是在Thread的静态块中来注册的,其作用就是注册一些本地方法提供给Thread类来使用,比如start0()、isAlive()… ,具体方法在一个C的文件中Thread.c,其定义了各个操作系统平台要用的关于线程的公共数据和操作,具体定义了很多的方法都是各个操作系统能公共调用的,也就是线程在构建时候,会将这些方法注册上去
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
- 3.start0() 会调用 JVM_StartThread 这个方法,JVM层面去启动一个线程
我们需要下载hotspot的源码才能找到,我们找到: jvm.cpp这个文件
JVM_ENTRY是用来定义 JVM_StartThread函数的,在这个函数里面创建了一个真正和平台有关的本地线程
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
...
native_thread = new JavaThread(&thread_entry, sz);
hotspot的源码中 thread.cpp文件中1558行的位置
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;
}
这个方法有两个参数,第一个是函数名称,线程创建成功之后会根据这个函数名称调用对应的函数;第二个是当前进程内已经有的线程数量。最后我们重点关注与一下 os::create_thread,实际就是调用平台创建线程的方法来创建线程。
- 4.其中start方法中会有一个函数调用,os::start_thread(thread);,调用平台启动线程的方法,最终会调用Thread.cpp文件中的JavaThread::run()方法
void Thread::start(Thread* thread) {
trace("start", thread);
// Start is different from resume in that its safety is guaranteed by context or
// being called from a Java method synchronized on the Thread object.
if (!DisableStartThread) {
if (thread->is_Java_thread()) {
// Initialize the thread state to RUNNABLE before starting this thread.
// Can not set it after the thread started because we do not know the
// exact thread state at that time. It could be in MONITOR_WAIT or
// in SLEEPING or some other state.
java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
java_lang_Thread::RUNNABLE);
}
os::start_thread(thread);
}
}
- 5.在其中JavaThread会进行一系列的初始化操作,最后有一个方法 thread_main_inner
void JavaThread::run() {
// initialize thread-local alloc buffer related fields
this->initialize_tlab();
// used to test validitity of stack trace backs
this->record_base_of_stack_pointer();
// Record real stack base and size.
this->record_stack_base_and_size();
// Initialize thread local storage; set before calling MutexLocker
this->initialize_thread_local_storage();
this->create_stack_guard_pages();
this->cache_global_variables();
// Thread is now sufficient initialized to be handled by the safepoint code as being
// in the VM. Change thread state from _thread_new to _thread_in_vm
ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);
assert(JavaThread::current() == this, "sanity check");
assert(!Thread::current()->owns_locks(), "sanity check");
DTRACE_THREAD_PROBE(start, this);
// This operation might block. We call that after all safepoint checks for a new thread has
// been completed.
this->set_active_handles(JNIHandleBlock::allocate_block());
if (JvmtiExport::should_post_thread_life()) {
JvmtiExport::post_thread_start(this);
}
EventThreadStart event;
if (event.should_commit()) {
event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
event.commit();
}
// We call another function to do the rest so we are sure that the stack addresses used
// from there will be lower than the stack base just computed
thread_main_inner();
// Note, thread is no longer valid at this point!
}
- 6.thread_main_inner 中,找到核心的代码块 this->entry_point()(this,this),在第四部中native_thread=newJavaThread(&thread_entry,sz); 的时候传递了一个thread_entry函数
void JavaThread::thread_main_inner() {
assert(JavaThread::current() == this, "sanity check");
assert(this->threadObj() != NULL, "just checking");
// Execute thread entry point unless this thread has a pending exception
// or has been stopped before starting.
// Note: Due to JVM_StopThread we can have pending exceptions already!
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;
}
- 7.我们找到thread_entry函数,找到vmSymbols::run_method_name()这个调用,通过回调方法调用Java线程中定义的run方法,让我们来看看, run_method_name是一个宏定义,在vmSymbols.hpp文件中可以找到
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);
}
- 8.找到VM_SYMBOLS_DO 函数,我们可以看到,其对应的就是一个,方法,
#define VM_SYMBOLS_DO(template, do_alias)
...
template(run_method_name, "run")
...
总结全文
其大概意思就是,Java里面创建线程之后,必须要调用start方法才能创建一个线程,该方法会通过虚拟机启动一个本地线程,本地线程的创建会调用当前系统去创建线程的方法进行创建线程,并且线程被执行的时候会回调 run方法进行业务逻辑的处理,大概意思就是,系统给你留了一个接口,你要去用这个接口调用你的run 方法,该线程提供给你一系列的操作。
参考:
https://blog.csdn.net/qq_41259576/article/details/107056383
https://blog.csdn.net/weixin_43490440/article/details/101519957
以上是关于thread start()方法源码解析的主要内容,如果未能解决你的问题,请参考以下文章
从Thread.start()方法看Thread源码,多次start一个线程会怎么样