网站服务器及运营维护公告,高端品牌网站建设案例,中学网站建设书,企业宣传片策划制作版本信息#xff1a; jdk版本#xff1a;jdk8u40 写在前面#xff1a;
大部分的Java程序员知道让线程睡眠的方法是Thread.sleep方法#xff0c;而这个方法是一个native方法#xff0c;让很多想知道底层如何让线程睡眠的程序员望而却步。所以笔者特意写在这篇文章#xf… 版本信息 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
对这里做一个简单的总结
改变状态为Sleeping如果开发者传入的毫秒为0这里会根据策略转换成yield如果不支持转换就会给出默认的睡眠时间因为对于线程的操作只能交给操作系统完成所以这里调用os::sleep方法接下来会重点分析此方法。如果睡眠过程中被中断了那么会抛出中断异常睡眠正常完成后会把状态改变成之前的状态。
因为我们只关心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这个可以理解为提供了底层睡眠和阻塞的API。判断是否可以响应中断如果响应中断那么每次循环都会判断是否被中断了获取当前时间此时间是纳秒纳秒转换成毫秒因为底层睡眠时间需要时毫秒单位这里为什么获取当前时间不直接拿毫秒因为考虑到精准度的问题调用parkEvent的park方法进入操作系统睡眠。
考虑到文章的篇幅问题parkEvent的park方法就不细追了。大家可以黑盒的理解它就是让当前线程去阻塞而传入的单位就是阻塞的时间。 总结
sleep的底层实现并不复杂但是不看源码是不会知道如果传入的时间为0会优化成yield方法并且在底层并不会像Object类中wait方法一样释放锁资源等等