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

这篇具有很好参考价值的文章主要介绍了JVM源码剖析之Thread类中sleep方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

版本信息:
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();
    // 把线程状态改变成SLEEPING
    thread->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方法一样,释放锁资源等等~文章来源地址https://www.toymoban.com/news/detail-727597.html

到了这里,关于JVM源码剖析之Thread类中sleep方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • JVM源码剖析之SymbolTable和StringTable

    JVM源码剖析之SymbolTable和StringTable

    很多读者在观看JVM相关的书籍时会看到SymbolTable和StringTable,书中的三言二语介绍的不是很清楚,并且读者的水平有限,导致无法理解SymbolTable和StringTable。所以特意写此篇图文并茂的文章来彻底理解SymbolTable和StringTable这两张表。 版本信息如下: 因为Hotspot是c++构成,所以也存

    2024年02月13日
    浏览(7)
  • 【C++】:STL源码剖析之vector类容器的底层模拟实现

    【C++】:STL源码剖析之vector类容器的底层模拟实现

    构造一个空vector size和capacity为0 将_start _finish _endofstorage 都置为空指针即可 传统写法 : 1). 新开辟一块和 v 同样容量的空间,更新 _start, _finish, _endofstorage 2). 将 v 中的数据拷贝到新开辟的空间中 注意 : 不要使用memcpy函数拷贝数据,如果数据是内置类型或浅拷贝的自定义类型

    2024年02月04日
    浏览(17)
  • 单元测试使用Thread.sleep()后线程直接停止

    单元测试使用Thread.sleep()后线程直接停止

    单元测试中测试多线程,使用sleep()阻塞线程,但是运行后发现Thread.sleep()后的代码不执行,直接退出了线程。 在单元测试中,如果子线程处于阻塞、死亡状态时,单元测试会立刻停止所有子线程。 如下图,不会输出running

    2024年02月13日
    浏览(7)
  • 01-从JDK源码级别剖析JVM类加载机制

    01-从JDK源码级别剖析JVM类加载机制

    上一篇:JVM虚拟机调优大全 当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM。 通过Java命令执行代码的大体流程如下: 其中loadClass的类加载过程有如下几步: 加载 验证 准备 解析 初始化 使用 卸载 加载:在硬盘上查找并通过IO读入字

    2024年02月09日
    浏览(11)
  • Java中TreeSet的基本介绍,细节讨论,使用注意事项,常用方法,底层源码分析

    TreeSet 是 Java 中的一个有序集合实现,它基于红黑树数据结构来存储元素, 可以保持元素的自然顺序(默认情况下升序)或者根据自定义比较器来进行排序 。下面是关于 TreeSet 的基本介绍、细节讨论、使用注意事项、常用方法以及一些底层实现细节。 基本介绍: TreeSet 是

    2024年02月11日
    浏览(17)
  • jvm深入研究文档--java中的堆--详解!--jvm底层探索(1)

    jvm深入研究文档--java中的堆--详解!--jvm底层探索(1)

    JVM的内存分区包括以下几个部分: 堆区(Heap) - 这是JVM的主要部分,用于存储实例对象和大多数Java对象,如数组和用户定义的类。 方法区(Method Area) - 这是线程私有的,用于存放类对象(加载好的类)。 栈区(Stack) - 这是线程私有的,包括虚拟机栈和本地方法栈。虚拟

    2024年02月07日
    浏览(15)
  • [.NET学习笔记] - Thread.Sleep与Task.Delay在生产中应用的性能测试

    [.NET学习笔记] - Thread.Sleep与Task.Delay在生产中应用的性能测试

    有个 Service 类,自己在内部实现 生产者/消费者 模式。即多个指令输入该服务后对象后, Service 内部有专门的消费线程执行传入的指令。每个指令的执行间隔为 1秒 。这里有两部分组成, 工作线程的载体。 new Thread 与 Task.Run 。 执行等待的方法。 Thread.Sleep 与 Task.Delay 。 cpu

    2024年02月09日
    浏览(15)
  • thread类中构造的函数参数必须是可拷贝的

    错误代码 这段代码会导致编译错误,因为在C++中,如果你尝试在线程(std::thread)中传递参数,那么这些参数必须是可拷贝的,或者你需要使用 std::ref 来传递可引用的参数。 在你的代码中,你尝试在线程中传递一个整数 3 和一个 std::string 引用 s,这是不允许的,因为 std::t

    2024年02月06日
    浏览(11)
  • Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制

    Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制

    Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制。该系列教程从Linux内核的各个模块入手,逐一分析其源码实现,并结合实际应用场景进行讲解。通过学习本系列,读者可以深入了解Linux操作系统的底层机制,掌握

    2024年01月21日
    浏览(13)
  • “深入剖析JVM内部原理:解密Java虚拟机的奥秘“

    标题:深入剖析JVM内部原理:解密Java虚拟机的奥秘 摘要:本文将深入探讨Java虚拟机(JVM)的内部原理,包括其架构、内存管理、垃圾回收机制、即时编译器等关键组成部分。通过解密JVM的奥秘,我们将更好地理解Java程序的执行过程,并能够优化代码的性能。 正文: 一、

    2024年02月13日
    浏览(14)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包