【學習背景】
今天主要是來了解Java執行緒Thread中的
run()、start()兩個方法的執行有哪些區別,會給出一個簡單的測驗代碼樣例,快速理解兩者的區別,再從原始碼層面去追溯start()底層是如何最終呼叫Thread#run()方法的,個人覺得這樣的學習不論對面試,還是實際編程來說都是比較有幫助的,
進入正文~
學習目錄
- 一、代碼測驗
- 二、原始碼分析
- 2.1 run()方法
- 2.2 start()方法
- 三、使用總結
一、代碼測驗
執行Thread的run()、start()方法的測驗代碼如下:
public class MyThread {
public static void print() {
System.out.println("print...");
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread() {
@Override
public void run() {
System.out.println("執行print()方法的執行緒是:" + Thread.currentThread().getName());
print();
}
};
System.out.println("一、呼叫run()方法的執行緒是:" + Thread.currentThread().getName());
thread1.run();
Thread.sleep(1000);
System.out.println("二、呼叫start()方法的執行緒是:" + Thread.currentThread().getName());
thread1.start();
}
}
測驗結果:
一、呼叫run()方法的執行緒是:main
執行print()方法的執行緒是:main
print...
main
二、呼叫start()方法的執行緒是:main
執行print()方法的執行緒是:Thread-0
print...
Thread-0
簡單總結:
- run()方法呼叫和執行重寫的代碼塊都是由main主執行緒來完成的;
- start()方法呼叫是有main()主執行緒來呼叫,但是重寫的代碼塊是由
非main主執行緒來執行的,
二、原始碼分析
2.1 run()方法
通過執行緒呼叫run()方法,這個其實很簡單,比如前面測驗代碼執行run方法:
thread1.run();
Ctrl + 滑鼠左鍵,進入到java.lang.Thread的run()方法的原始碼:
@Override
public void run() {
if (target != null) {
target.run();
}
}
再Ctrl + Alt + 滑鼠左鍵,定位前面測驗類MyThread中重寫的run()方法

可以發現,直接就呼叫到重寫的run()方法了,并且,你可以重復呼叫多次,而且每一次都是由main主執行緒呼叫run()方法,然后非main主執行緒,執行run()方法代碼塊

雖然run()方法也可以直接執行到重寫的代碼塊,不過實際當中,按規范來說,不會直接呼叫run()執行,而是通過start()方法來開啟執行緒,start()方法底層會判斷執行緒的狀態,開啟執行緒,最終呼叫run()方法并結束執行緒狀態,往后了解,
2.2 start()方法
Oracle JDK原始碼stat()方法:
public class Thread implements Runnable {
...
private ThreadGroup group;
private volatile int threadStatus = 0;
...
public synchronized void start() {
//如果執行緒狀態不是0-新建,則拋例外
if (threadStatus != 0)
throw new IllegalThreadStateException();
//將當前執行緒添加到執行緒組
group.add(this);
//執行緒是否開啟,默認false-未開啟
boolean started = false;
try {
//呼叫native本地方法(最終呼叫run()方法的核心入口)
start0();
//執行完start0,說明執行緒已正常啟動
started = true;
} finally {
try {
if (!started) {
//執行緒啟動失敗,則從執行緒組中將該執行緒洗掉
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
...
}
}
}
難點和重點是呼叫的start0()方法,這是一個native(本地方法/底層方法),原始碼:
//native本地方法,底層開啟異步執行緒,由JVM創建并啟動執行緒,并最終呼叫run()方法
private native void start0();
由于Oracle JDK對于native本地方法的原始碼隱藏了具體實作,可以去Open JDK官方或Github下載原始碼
以下載open jdk8最新的原始碼版本jdk8u60為例,需要下載jdk和hotspot兩種原始碼來研究
- 下載
jdk8u60原始碼
http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767

- 下載jdk8u60的
hotspot原始碼
http://hg.openjdk.java.net/jdk8u/jdk8u60/hotspot/file/37240c1019fd

最后發現Open JDK官網下載實在是太慢了,而且還需要分別下載jdk8u60和jdk8u60_hotspot的原始碼

不過現在OpenJDK的原始碼已經放到GitHub,可以直接去GitHub下載,而且jdk原始碼和hotspot原始碼直接放一起了,省事,這里以下載jdk8-b60示例,下載地址:
https://github.com/openjdk/jdk/releases/tag/jdk8-b60
下載完解壓,主要關注jdk和hotspot這兩個原始碼目錄:

使用相關工具,比如Scource Insight查看底層方法native stat0具體原始碼實作
(教程)安裝Scource Insight
(教程)使用Scource Insight打開OpenJDK原始碼

接下來通過Scource Insight查看底層方法start0是如何一步步,最終由主執行緒呼叫Thread#run()方法的原始碼分析程序
首先查看
原始碼檔案:\jdk-jdk8-b60\jdk\src\share\native\java\lang\ Thread.c
關鍵原始碼:JVM_StartThread
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},//JVM開啟執行緒的主入口
{"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},
};
原始碼檔案:\jdk-jdk8-b60\hotspot\src\share\vm\prims\ jvm.cpp
關鍵原始碼:new JavaThread
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));
size_t sz = size > 0 ? (size_t) size : 0;
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
原始碼檔案:\jdk-jdk8-b60\hotspot\src\share\vm\prims\ jvm.cpp
關鍵原始碼:set_thread_status*RUNNABLE**
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);//設定執行緒狀態為RUNNABLE-運行狀態
}
os::start_thread(thread);
}
}
原始碼檔案:\jdk-jdk8-b60\hotspot\src\share\vm\runtime\ os.cpp
關鍵原始碼:osthread->set_state(RUNNABLE)
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);//設定執行緒狀態為RUNNABLE-運行狀態, 子執行緒可以開始執行thread->run()
pd_start_thread(thread);
}
原始碼檔案:\jdk-jdk8-b60\hotspot\src\os\linux\vm\ os_linux.cpp
關鍵原始碼:sync_with_child->notify()
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);
//這里就是最終父執行緒呼叫thread->run()方法的主入口
sync_with_child->notify();
}
所以呼叫執行緒的start()方法,底層主要執行程序為:

三、使用總結
run()和start()方法的區別?
- 呼叫run()方法和執行重寫run()方法的代碼塊都是由main主執行緒來完成的;
- 呼叫start()方法是由main主執行緒來呼叫,但是重寫的run()方法代碼塊是由
非main主執行緒來執行的,- 除此之外,start()方法JDK底層Thread原始碼的start()方法中,會判斷執行緒狀態是否是新建狀態,如果不是則拋例外
IllegalThreadStateException-執行緒狀態不合法例外- 并且后續會呼叫一個native start0()本地方法,本地方法是底層方法,Oracle JDK原始碼隱藏了native的具體實作,通過Open JDK的jdk原始碼和hotspot原始碼,查看native start0的底層原始碼,可以看到主要由c和c++實作的,start0會通過
JVM_StartThread開啟執行緒,并new JavaThread一個native_thread本地執行緒,然后設定執行緒狀態為RUNNABLE運行狀態,最后通過一個notify()方法實作主執行緒呼叫Thread#run()方法,程式結束后,執行緒狀態會變成終止狀態- 因此實際開發當中,應該規范的通過執行緒呼叫start()方法來開啟執行緒,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/303348.html
標籤:其他
上一篇:企業級springboot落地:連接內外網郵箱,實作郵件提醒功能
下一篇:硬體工程師其實拼的是細節
