版本信息:
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
对这里做一个简单的总结:
因为我们只关心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 ;
- }
- }
对这里做一个简单的总结:
考虑到文章的篇幅问题,parkEvent的park方法就不细追了。大家可以黑盒的理解,它就是让当前线程去阻塞,而传入的单位就是阻塞的时间。
sleep的底层实现并不复杂,但是不看源码是不会知道,如果传入的时间为0会优化成yield方法,并且在底层并不会像Object类中wait方法一样,释放锁资源等等~