➜ jdk hg id 76072a077ee1 jdk-11 28
首先看下sleep方法
public static native void sleep(long millis) throws InterruptedException;
该方法是个native方法,看下其对应的JVM内部的代码
代码语言:javascript复制// src/hotspot/share/prims/jvm.cpp
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
...
if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
...
if (millis == 0) {
...
} else {
...
if (os::sleep(thread, millis, true) == OS_INTRPT) {
...
if (!HAS_PENDING_EXCEPTION) {
...
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
}
...
}
...
JVM_END
由上面的代码我们可以看到,如果在调用sleep之前或在sleep过程中,sleep线程被interrupt了,则该sleep方法会抛出InterruptedException异常。
我们继续看下os::sleep方法
代码语言:javascript复制// src/hotspot/os/posix/os_posix.cpp
int os::sleep(Thread* thread, jlong millis, bool interruptible) {
...
ParkEvent * const slp = thread->_SleepEvent ;
...
if (interruptible) {
...
for (;;) {
if (os::is_interrupted(thread, true)) {
return OS_INTRPT;
}
...
if (newtime - prevtime < 0) {
...
} else {
millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
}
if (millis <= 0) {
return OS_OK;
}
...
{
...
slp->park(millis);
...
}
}
}
...
}
该方法最终会进入到一个无限for循环里,在for循环中,会首先检查该线程是否被interrupt了,如果是,则返回OS_INTRPT,上层方法会根据该返回进一步抛出InterruptedException异常。
如果该线程没有被interrupt,该次for循环则会最终执行thread->_SleepEvent的park方法,堵塞这个线程millis时间。
正常情况下,park方法会在阻塞线程millis时间后返回,然后进入下次for循环,并检测到剩余要阻塞时间小于等于0,进而返回OS_OK来结束该方法。
但当park方法在阻塞过程中,该线程的interrupt方法又被调用了,park方法则会提前退出,下次for循环检测到线程状态为interrupted,进而返回OS_INTRPT,结束该方法。
Thread.sleep方法的大致流程就是这个样子。
不过,为了能更加清楚的知道线程是如何堵塞的,以及是如何从阻塞状态返回的,我们还是再看下_SleepEvent对应的类以及其park和unpark方法。
_SleepEvent对应的类
代码语言:javascript复制// src/hotspot/share/runtime/park.hpp
class ParkEvent : public os::PlatformEvent {
...
} ;
该类继承了os::PlatformEvent类,继续看下这个类
代码语言:javascript复制// src/hotspot/os/posix/os_posix.hpp
class PlatformEvent : public CHeapObj<mtInternal> {
private:
...
volatile int _event; // Event count/permit: -1, 0 or 1
volatile int _nParked; // Indicates if associated thread is blocked: 0 or 1
pthread_mutex_t _mutex[1]; // Native mutex for locking
pthread_cond_t _cond[1]; // Native condition variable for blocking
...
public:
...
void park();
int park(jlong millis);
void unpark();
...
};
由上面的代码我们能大概了解到ParkEvent的数据结构。我们再来看下其park/unpark方法的逻辑。
首先是ParkEvent.park方法
代码语言:javascript复制// src/hotspot/os/posix/os_posix.cpp
int os::PlatformEvent::park(jlong millis) {
...
int v;
...
for (;;) {
v = _event;
if (Atomic::cmpxchg(v - 1, &_event, v) == v) break;
}
...
if (v == 0) { // Do this the hard way by blocking ...
struct timespec abst;
...
to_abstime(&abst, millis * (NANOUNITS / MILLIUNITS), false);
int ret = OS_TIMEOUT;
int status = pthread_mutex_lock(_mutex);
...
_nParked;
while (_event < 0) {
status = pthread_cond_timedwait(_cond, _mutex, &abst);
...
if (status == ETIMEDOUT) break;
}
--_nParked;
if (_event >= 0) {
ret = OS_OK;
}
_event = 0;
status = pthread_mutex_unlock(_mutex);
...
return ret;
}
return OS_OK;
}
其次是ParkEvent.unpark方法
代码语言:javascript复制// src/hotspot/os/posix/os_posix.cpp
void os::PlatformEvent::unpark() {
...
if (Atomic::xchg(1, &_event) >= 0) return;
int status = pthread_mutex_lock(_mutex);
...
int anyWaiters = _nParked;
...
status = pthread_mutex_unlock(_mutex);
...
if (anyWaiters != 0) {
status = pthread_cond_signal(_cond);
...
}
}
这两个方法都是通过_event和_nParked这两个字段的值,来得知线程是否应该阻塞等待,或者是否应该通知线程从阻塞等待状态中返回。
比如,在park方法中,如果检测到_event值不等于0,则说明unpark方法被调用过,不用等待了。如果unpark方法没被调用过,则会进入一般逻辑,即调用pthread_cond_timedwait方法堵塞该线程millis时间。
同样的道理,在unpark方法中,先检查_event值,如果_event大于等于0,则说明park方法没调用过,也就无需执行后面的逻辑了。如果park方法被调用过,则需要再进一步加锁检查_nParked字段,如果_nParked还是不等于0,说明该线程确实是在堵塞等待过程中,调用pthread_cond_signal方法,告诉这个线程,其可以从阻塞状态中退出了。
ParkEvent.park/unpark方法的大体逻辑就是这样。
有关Thread.interrupt方法是如何调用_SleepEvent的unpark方法,使Thread.sleep从sleeping状态退出的,请查看文章:
Java中Thread.interrupt源码分析