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


    版本信息:
    jdk版本:jdk8u40

    写在前面:

    大部分的Java程序员知道让线程睡眠的方法是Thread.sleep方法,而这个方法是一个native方法,让很多想知道底层如何让线程睡眠的程序员望而却步。所以笔者特意写在这篇文章,带各位读者剖析一下Thread.sleep方法背后的神秘。

    源码剖析:

    话不多说,先从Java层面看一下sleep这个方法。

    1. public static native void sleep(long millis) throws InterruptedException;
    2. public static void sleep(long millis, int nanos)
    3. throws InterruptedException {
    4. // 非法逻辑
    5. if (millis < 0) {
    6. throw new IllegalArgumentException("timeout value is negative");
    7. }
    8. // 非法逻辑
    9. if (nanos < 0 || nanos > 999999) {
    10. throw new IllegalArgumentException(
    11. "nanosecond timeout value out of range");
    12. }
    13. // 如果大于500000就算一毫秒,如果没有设置毫秒,那么纳秒单位就四舍五入算一毫秒。
    14. if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
    15. millis++;
    16. }
    17. // 调用重载的sleep方法。
    18. sleep(millis);
    19. }

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

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

    1. static JNINativeMethod methods[] = {
    2. {"start0", "()V", (void *)&JVM_StartThread},
    3. {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
    4. {"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
    5. {"suspend0", "()V", (void *)&JVM_SuspendThread},
    6. {"resume0", "()V", (void *)&JVM_ResumeThread},
    7. {"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
    8. {"yield", "()V", (void *)&JVM_Yield},
    9. {"sleep", "(J)V", (void *)&JVM_Sleep},
    10. {"currentThread", "()" THD, (void *)&JVM_CurrentThread},
    11. {"countStackFrames", "()I", (void *)&JVM_CountStackFrames},
    12. {"interrupt0", "()V", (void *)&JVM_Interrupt},
    13. {"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted},
    14. {"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    15. {"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
    16. {"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    17. {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
    18. };

    这里是一个Thread类中所有native方法的映射表,我们看到sleep映射为JVM_Sleep方法。

    所以看到 src/share/vm/prims/jvm.cpp 文件中 JVM_Sleep方法

    1. JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
    2. JVMWrapper("JVM_Sleep");
    3. // 改变状态为sleeping中。
    4. JavaThreadSleepState jtss(thread);
    5. EventThreadSleep event;
    6. if (millis == 0) {
    7. // 如果传入的毫秒为0,那么底层为转换为yield方法,而yield仅仅是让出CPU的使用权,让当前线程重新等待被调度
    8. if (ConvertSleepToYield) {
    9. os::yield();
    10. } else {
    11. // 如果不支持转换为yield方法,那么会给出一个默认的睡眠时间。
    12. ThreadState old_state = thread->osthread()->get_state();
    13. thread->osthread()->set_state(SLEEPING);
    14. os::sleep(thread, MinSleepInterval, false);
    15. thread->osthread()->set_state(old_state);
    16. }
    17. } else {
    18. // 拿到线程在sleep之前的状态。
    19. ThreadState old_state = thread->osthread()->get_state();
    20. // 把线程状态改变成SLEEPING
    21. thread->osthread()->set_state(SLEEPING);
    22. // 因为对于线程的操作只能交给操作系统
    23. if (os::sleep(thread, millis, true) == OS_INTRPT) {
    24. // 如果睡眠期间被中断,那么抛出中断异常。
    25. THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
    26. }
    27. // 改回之前的状态。
    28. thread->osthread()->set_state(old_state);
    29. }
    30. JVM_END

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

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

    因为我们只关心Linux操作系统,所以看到src/os/linux/vm/os_linux.cpp 文件中sleep方法。

    1. int os::sleep(Thread* thread, jlong millis, bool interruptible) {
    2. ParkEvent * const slp = thread->_SleepEvent ;
    3. slp->reset() ;
    4. OrderAccess::fence() ;
    5. // 判断是否响应中断。
    6. if (interruptible) {
    7. // 拿到进入之前的时间(纳米为单位)
    8. jlong prevtime = javaTimeNanos();
    9. for (;;) {
    10. // 如果被中断了。
    11. if (os::is_interrupted(thread, true)) {
    12. return OS_INTRPT;
    13. }
    14. // 拿到最新的时间(纳米为单位)
    15. jlong newtime = javaTimeNanos();
    16. if (newtime - prevtime < 0) {
    17. // 最新的时间小于之前的时间,这不是扯淡么。
    18. assert(!Linux::supports_monotonic_clock(), "time moving backwards");
    19. } else {
    20. // 一秒 = 1000毫秒
    21. // 一秒 = 1000000000纳秒
    22. // NANOSECS_PER_MILLISEC = 1000000
    23. // 这里是获取到当前睡眠的时间,并且从纳秒转换成毫秒。
    24. millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
    25. }
    26. // 时间到了,直接退出。
    27. if(millis <= 0) {
    28. return OS_OK;
    29. }
    30. prevtime = newtime;
    31. {
    32. JavaThread *jt = (JavaThread *) thread;
    33. ThreadBlockInVM tbivm(jt);
    34. // 改变线程状态。
    35. OSThreadWaitState osts(jt->osthread(), false /* not Object.wait() */);
    36. jt->set_suspend_equivalent();
    37. // 睡眠
    38. slp->park(millis);
    39. }
    40. }
    41. } else {
    42. OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
    43. jlong prevtime = javaTimeNanos();
    44. for (;;) {
    45. jlong newtime = javaTimeNanos();
    46. if (newtime - prevtime < 0) {
    47. assert(!Linux::supports_monotonic_clock(), "time moving backwards");
    48. } else {
    49. millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
    50. }
    51. if(millis <= 0) break ;
    52. prevtime = newtime;
    53. slp->park(millis);
    54. }
    55. return OS_OK ;
    56. }
    57. }

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

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

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

    总结:

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

  • 相关阅读:
    IS-IS 路由选择协议入门
    2023年中国乳胶制品产量、需求量及市场规模分析[图]
    实验三:机器学习1.0
    Linux应急响应笔记
    神经网络算法用什么语言,神经网络是一种算法吗
    java打印语句:sex8.rar670
    Java8 BiConsumer<T, U> 函数接口浅析分享(含示例,来戳!)
    LabVIEW在无线设备中的应用
    计算机网络期末知识点(第三章-数据链路层)
    C语言实验五 循环结构程序设计(二)
  • 原文地址:https://blog.csdn.net/qq_43799161/article/details/133684254