版本信息:
jdk版本:jdk8u40
寫在前面:
大部分的Java程序員知道讓線程睡眠的方法是Thread.sleep方法,而這個方法是一個native方法,讓很多想知道底層如何讓線程睡眠的程序員望而卻步。所以筆者特意寫在這篇文章,帶各位讀者剖析一下Thread.sleep方法背后的神秘。
源碼剖析:
話不多說,先從Java層面看一下sleep這個方法。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos)
throws InterruptedException {
// 非法邏輯
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// 非法邏輯
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
// 如果大于500000就算一毫秒,如果沒有設(shè)置毫秒,那么納秒單位就四舍五入算一毫秒。
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
// 調(diào)用重載的sleep方法。
sleep(millis);
}
這是一個重載的方法,可以單獨(dú)傳入毫秒,也可以傳入毫秒和納秒。不管調(diào)用哪一個sleep最終都是調(diào)用native的sleep方法,所以接下來需要看底層如何對其實(shí)現(xiàn)。
src/share/native/java/lang/Thread.c 文件中有定義sleep的native實(shí)現(xiàn)方法。
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},
};
這里是一個Thread類中所有native方法的映射表,我們看到sleep映射為JVM_Sleep方法。
所以看到 src/share/vm/prims/jvm.cpp 文件中?JVM_Sleep方法
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
JVMWrapper("JVM_Sleep");
// 改變狀態(tài)為sleeping中。
JavaThreadSleepState jtss(thread);
EventThreadSleep event;
if (millis == 0) {
// 如果傳入的毫秒為0,那么底層為轉(zhuǎn)換為yield方法,而yield僅僅是讓出CPU的使用權(quán),讓當(dāng)前線程重新等待被調(diào)度
if (ConvertSleepToYield) {
os::yield();
} else {
// 如果不支持轉(zhuǎn)換為yield方法,那么會給出一個默認(rèn)的睡眠時間。
ThreadState old_state = thread->osthread()->get_state();
thread->osthread()->set_state(SLEEPING);
os::sleep(thread, MinSleepInterval, false);
thread->osthread()->set_state(old_state);
}
} else {
// 拿到線程在sleep之前的狀態(tài)。
ThreadState old_state = thread->osthread()->get_state();
// 把線程狀態(tài)改變成SLEEPING
thread->osthread()->set_state(SLEEPING);
// 因為對于線程的操作只能交給操作系統(tǒng)
if (os::sleep(thread, millis, true) == OS_INTRPT) {
// 如果睡眠期間被中斷,那么拋出中斷異常。
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
// 改回之前的狀態(tài)。
thread->osthread()->set_state(old_state);
}
JVM_END
對這里做一個簡單的總結(jié):
- 改變狀態(tài)為Sleeping
- 如果開發(fā)者傳入的毫秒為0,這里會根據(jù)策略轉(zhuǎn)換成yield,如果不支持轉(zhuǎn)換就會給出默認(rèn)的睡眠時間
- 因為對于線程的操作只能交給操作系統(tǒng)完成,所以這里調(diào)用os::sleep方法,接下來會重點(diǎn)分析此方法。
- 如果睡眠過程中被中斷了,那么會拋出中斷異常
- 睡眠正常完成后,會把狀態(tài)改變成之前的狀態(tài)。
因為我們只關(guān)心Linux操作系統(tǒng),所以看到src/os/linux/vm/os_linux.cpp 文件中sleep方法。
int os::sleep(Thread* thread, jlong millis, bool interruptible) {
ParkEvent * const slp = thread->_SleepEvent ;
slp->reset() ;
OrderAccess::fence() ;
// 判斷是否響應(yīng)中斷。
if (interruptible) {
// 拿到進(jìn)入之前的時間(納米為單位)
jlong prevtime = javaTimeNanos();
for (;;) {
// 如果被中斷了。
if (os::is_interrupted(thread, true)) {
return OS_INTRPT;
}
// 拿到最新的時間(納米為單位)
jlong newtime = javaTimeNanos();
if (newtime - prevtime < 0) {
// 最新的時間小于之前的時間,這不是扯淡么。
assert(!Linux::supports_monotonic_clock(), "time moving backwards");
} else {
// 一秒 = 1000毫秒
// 一秒 = 1000000000納秒
// NANOSECS_PER_MILLISEC = 1000000
// 這里是獲取到當(dāng)前睡眠的時間,并且從納秒轉(zhuǎn)換成毫秒。
millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
}
// 時間到了,直接退出。
if(millis <= 0) {
return OS_OK;
}
prevtime = newtime;
{
JavaThread *jt = (JavaThread *) thread;
ThreadBlockInVM tbivm(jt);
// 改變線程狀態(tài)。
OSThreadWaitState osts(jt->osthread(), false /* not Object.wait() */);
jt->set_suspend_equivalent();
// 睡眠
slp->park(millis);
}
}
} else {
OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
jlong prevtime = javaTimeNanos();
for (;;) {
jlong newtime = javaTimeNanos();
if (newtime - prevtime < 0) {
assert(!Linux::supports_monotonic_clock(), "time moving backwards");
} else {
millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
}
if(millis <= 0) break ;
prevtime = newtime;
slp->park(millis);
}
return OS_OK ;
}
}
對這里做一個簡單的總結(jié):
- 拿到當(dāng)前線程對應(yīng)的parkEvent,這個可以理解為提供了底層睡眠和阻塞的API。
- 判斷是否可以響應(yīng)中斷
- 如果響應(yīng)中斷,那么每次循環(huán)都會判斷是否被中斷了
- 獲取當(dāng)前時間,此時間是納秒
- 納秒轉(zhuǎn)換成毫秒,因為底層睡眠時間需要時毫秒單位(這里為什么獲取當(dāng)前時間不直接拿毫秒,因為考慮到精準(zhǔn)度的問題)
- 調(diào)用parkEvent的park方法,進(jìn)入操作系統(tǒng)睡眠。
考慮到文章的篇幅問題,parkEvent的park方法就不細(xì)追了。大家可以黑盒的理解,它就是讓當(dāng)前線程去阻塞,而傳入的單位就是阻塞的時間。文章來源:http://www.zghlxwxcb.cn/news/detail-727597.html
總結(jié):
sleep的底層實(shí)現(xiàn)并不復(fù)雜,但是不看源碼是不會知道,如果傳入的時間為0會優(yōu)化成yield方法,并且在底層并不會像Object類中wait方法一樣,釋放鎖資源等等~文章來源地址http://www.zghlxwxcb.cn/news/detail-727597.html
到了這里,關(guān)于JVM源碼剖析之Thread類中sleep方法的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!