当前位置:首页 > 编程笔记 > 正文
已解决

JVM源码剖析之Thread类中sleep方法

来自网友在路上 163863提问 提问时间:2023-10-10 20:38:46阅读次数: 63

最佳答案 问答题库638位专家为你答疑解惑

版本信息:
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就算一毫秒,如果没有设置毫秒,那么纳秒单位就四舍五入算一毫秒。if (nanos >= 500000 || (nanos != 0 && millis == 0)) {millis++;}// 调用重载的sleep方法。sleep(millis);
}

这是一个重载的方法,可以单独传入毫秒,也可以传入毫秒和纳秒。不管调用哪一个sleep最终都是调用native的sleep方法,所以接下来需要看底层如何对其实现。

src/share/native/java/lang/Thread.c 文件中有定义sleep的native实现方法。

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");// 改变状态为sleeping中。JavaThreadSleepState jtss(thread);EventThreadSleep event;if (millis == 0) {		// 如果传入的毫秒为0,那么底层为转换为yield方法,而yield仅仅是让出CPU的使用权,让当前线程重新等待被调度if (ConvertSleepToYield) {os::yield();} else {// 如果不支持转换为yield方法,那么会给出一个默认的睡眠时间。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之前的状态。ThreadState old_state = thread->osthread()->get_state();// 把线程状态改变成SLEEPINGthread->osthread()->set_state(SLEEPING);// 因为对于线程的操作只能交给操作系统if (os::sleep(thread, millis, true) == OS_INTRPT) {// 如果睡眠期间被中断,那么抛出中断异常。THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");}// 改回之前的状态。thread->osthread()->set_state(old_state);}
JVM_END

对这里做一个简单的总结:

  1. 改变状态为Sleeping
  2. 如果开发者传入的毫秒为0,这里会根据策略转换成yield,如果不支持转换就会给出默认的睡眠时间
  3. 因为对于线程的操作只能交给操作系统完成,所以这里调用os::sleep方法,接下来会重点分析此方法。
  4. 如果睡眠过程中被中断了,那么会抛出中断异常
  5. 睡眠正常完成后,会把状态改变成之前的状态。

因为我们只关心Linux操作系统,所以看到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() ;// 判断是否响应中断。if (interruptible) {// 拿到进入之前的时间(纳米为单位)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// 这里是获取到当前睡眠的时间,并且从纳秒转换成毫秒。millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;}// 时间到了,直接退出。if(millis <= 0) {return OS_OK;}prevtime = newtime;{JavaThread *jt = (JavaThread *) thread;ThreadBlockInVM tbivm(jt);// 改变线程状态。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 ;}
}

对这里做一个简单的总结:

  1. 拿到当前线程对应的parkEvent,这个可以理解为提供了底层睡眠和阻塞的API。
  2. 判断是否可以响应中断
  3. 如果响应中断,那么每次循环都会判断是否被中断了
  4. 获取当前时间,此时间是纳秒
  5. 纳秒转换成毫秒,因为底层睡眠时间需要时毫秒单位(这里为什么获取当前时间不直接拿毫秒,因为考虑到精准度的问题)
  6. 调用parkEvent的park方法,进入操作系统睡眠。

考虑到文章的篇幅问题,parkEvent的park方法就不细追了。大家可以黑盒的理解,它就是让当前线程去阻塞,而传入的单位就是阻塞的时间。

总结:

sleep的底层实现并不复杂,但是不看源码是不会知道,如果传入的时间为0会优化成yield方法,并且在底层并不会像Object类中wait方法一样,释放锁资源等等~

查看全文

99%的人还看了

猜你感兴趣

版权申明

本文"JVM源码剖析之Thread类中sleep方法":http://eshow365.cn/6-18556-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!