前言
在日常编码的过程中,我们经常会使用Thread.sleep、LockSupport.park()主动阻塞线程,或者使用synchronized和Object.wait来阻塞线程保证并发安全。此时我们会发现,对于Thread.sleep和Object.wait方法是会抛出InterruptedException,而LockSupport.park()和synchronized则不会。而当我们调用Thread.interrupt方法时,除了synchronized,其他线程阻塞的方式都会被唤醒。
于是本文就来探究一下Thread.sleep、LockSupport.park()、synchronized和Object.wait的线程阻塞的原理以及InterruptedException的本质
本文主要分为以下几个部分
1.Thread.sleep的原理
2.LockSupport.park()的原理
3.synchronized线程阻塞的原理
4.ParkEvent和parker对象的原理
5.Thread.interrupt的原理
6.对于synchronized打断原理的扩展
1.Thread.sleep的原理
Thread.java
首先还是从java入手,查看sleep方法,可以发现它直接就是一个native方法:
public static native void sleep(long millis) throws InterruptedException;
为了查看native方法的具体逻辑,我们就需要下载openjdk和hotspot的源码了,下载地址:http://hg.openjdk.java.net/jdk8
查看Thread.c:jdk源码目录src/java.base/share/native/libjava
可以看到对应的jvm方法是JVM_Sleep:
static JNINativeMethod methods[] = {
...
{
"sleep", "(J)V", (void *)&JVM_Sleep},
...
};
查看jvm.cpp,hotspot目录src/share/vm/prims
找到JVM_Sleep方法,我们关注其重点逻辑:
方法的逻辑中,首先会做2个校验,分别是睡眠时间和线程的打断标记。其实这2个数据的校验都是可以放到java层,不过jvm的设计者将其放到了jvm的逻辑中去判断。
如果睡眠的时间为0,那么会调用系统级别的睡眠方法os::sleep(),睡眠时间为最小时间间隔。在睡眠之前会保存线程当前的状态,并将其设置为SLEEPING。在睡眠结束之后恢复线程状态。
接着就是sleep方法的重点,如果睡眠时间不为0,同样需要保存和恢复线程的状态,并调用系统级别的睡眠方法os::sleep()。当然睡眠的时间会变成指定的毫秒数。
最重要的区别是,此时会判断os::sleep()的返回值,如果是打断状态,那么就会抛出一个InterruptException!这里其实就是InterruptException产生的源头
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
JVMWrapper("JVM_Sleep");
//如果睡眠的时间小于0,则抛出异常。这里数据的校验在jvm层逻辑中校验
if (millis < 0) {
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
}
//如果线程已经被打断了,那么也抛出异常
if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
...
//这里允许睡眠时间为0
if (millis == 0) {
...{
//获取并保存线程的旧状态
ThreadState old_state = thread->osthread()->get_state();
//将线程的状态设置为SLEEPING
thread->osthread()->set_state(SLEEPING);
//调用系统级别的sleep方法,此时只会睡眠最小时间间隔
os::sleep(thread, MinSleepInterval, false);
//恢复线程的状态
thread->osthread()->set_state(old_state);
}
} else {
//获取并保存线程的旧状态
ThreadState old_state = thread->osthread()->get_state();
//将线程的状态设置为SLEEPING
thread->osthread()->set_state(SLEEPING);
//睡眠指定的毫秒数,并判断返回值
if (os::sleep(thread, millis, true) == OS_INTRPT) {
...
//抛出InterruptedException异常
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
//恢复线程的状态
thread->osthread()->set_state(old_state);
}
JVM_END
查看os_posix.cpp,hotspot目录src/os/posix/vm
我们接着查看os::sleep()方法:
首先获取线程的SleepEvent对象,这个是线程睡眠的关键
根据是否允许打断分为2个大分支,其中逻辑大部分是相同的,区别在于允许打断的分支中会在循环中额外判断打断标记,如果打断标记为true,则返回打断状态,并在外层方法中抛出InterruptedException
最终线程睡眠是调用SleepEvent对象的park方法完成的,该对象内部的原理后面统一说
int os::sleep(Thread* thread, jlong millis, bool interruptible) {
//获取thread中的_SleepEvent对象
ParkEvent * const slp = thread->_SleepEvent ;
...
//如果是允许被打断
if (interruptible) {
//记录下当前时间戳,这是时间比较的基准
jlong prevtime = javaTimeNanos