前言
Java 中有时需要将线程进入睡眠状态,这时一般我们就会通过Thread.sleep
使线程进入睡眠状态,接下去就看看执行该语句在 JVM 中做了什么。
简单例子
以下是一个简单的例子,使主线程睡眠5秒钟。
public class TestSleep {
public static void main(String[] args) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
JVM 中的线程
在继续往 JVM 层看start0
本地方法前,我们先了解下 JVM 中的相关线程,这将有助于后面更好理解 Java 层线程与 JVM 中的线程对应关系。
在 JVM 中,也用 C++ 定义了一些 Thread 类,它们的继承结构如下,其中对于 Java 层线程到 JVM 层主要相关的有 Java 层的 java.lang.Thread、JavaThread 和 OSThread。
- java.lang.Thread 属于 Java 层的线程对象,每个 Java 层对象都会在 JVM 中使用 oop 来表示,所以它也会在 JVM 中产生一个 oop。
- Thread 是 C++ 定义的线程基类,除了 OSThread 类,作为其他线程的基类,它包含了 OSThread 对象的指针。
- JavaThread 是 C++ 定义的线程类,我们在 Java 层创建的线程对象会使用 JavaThread 对象来表示,它包含了指向线程的 oop 的指针。
- OSThread 是 C++ 定义的线程,它不与其他线程构成继承关系,它是 JVM 对不同操作系统的线程的统一抽象,它维护了操作系统线程的句柄,用于获取操作系统的线程。
--Thread
--JavaThread
--CodeCacheSweeperThread
--CompilerThread
--JvmtiAgentThread
--ServiceThread
--NamedThread
--ConcurrentGCThread
--VMThread
--WorkerThread
--AbstractGangWorker
--GCTaskThread
--WatcherThread
--OSThread
复制代码
sleep方法
在 Thread 类中,sleep
是一个静态且本地方法。
public static native void sleep(long millis) throws InterruptedException;
复制代码
Thread.c
Java 层声明的本地方法对应实现在 Thread.c 中,sleep
是一个注册到 JVM 中的方法,它与 JVM 中的JVM_Sleep
函数绑定了,所以实现逻辑在JVM_Sleep
函数里。逻辑为:
JVMWrapper("JVM_Sleep")
用于调试。- 睡眠时间不能为负。
- 是否已经被中断了。
JavaThreadSleepState jtss(thread)
用于修改线程状态并做一些统计,当睡眠结束后,会修改回线程状态,在 JavaThreadSleepState 的析构函数中修改。- 睡眠时间如果为0,则根据
ConvertSleepToYield
做不同处理,它表示是否将 sleep 操作转为 yield 操作。分别调用os::naked_yield
和os::sleep
处理,封装了不同操作系统的调用实现,后面以 Windows 为例分别看相应实现。 - 通过
thread->osthread()->get_state()
获取 OSThread 对象,并将其状态设置为SLEEPING
等到 sleep 结束后设置回原来的状态。 - 如果睡眠时间大于0,则做类似操作,不过它支持中断。
- 发送事件,结束。
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
JVMWrapper("JVM_Sleep");
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");
}
JavaThreadSleepState jtss(thread);
HOTSPOT_THREAD_SLEEP_BEGIN(millis);
EventThreadSleep event;
if (millis == 0) {
if (ConvertSleepToYield) {
os::naked_yield();
} else {
ThreadState old_state = thread->osthread()->get_state();
thread->osthread()->set_state(SLEEPING);
os::sleep(thread, MinSleepInterval, false);
thread->osthread()->set_state(old_state);
}
} else {
ThreadState old_state = thread->osthread()->get_state();
thread->osthread()->set_state(SLEEPING);
if (os::sleep(thread, millis, true) == OS_INTRPT) {
if (!HAS_PENDING_EXCEPTION) {
if (event.should_commit()) {
event.set_time(millis);
event.commit();
}
HOTSPOT_THREAD_SLEEP_END(1);
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
}
thread->osthread()->set_state(old_state);
}
if (event.should_commit()) {
event.set_time(millis);
event.commit();
}
HOTSPOT_THREAD_SLEEP_END(0);
JVM_END
复制代码
os::naked_yield
naked_yield
函数的实现很简单,就直接调用SwitchToThread
系统函数。通过该函数可以让系统查看是否有其他线程迫切需要CPU,将CPU让给其他线程,如果没有其他线程则立即返回。
void os::naked_yield() {
SwitchToThread();
}
复制代码
os::sleep
- 获取最大限制大小limit。
- 如果超过 limit 则通过减法将其转成多次递归调用
sleep
函数。 - 获取 OSThread 对象,然后通过 OSThreadWaitState 设置线程状态为等待,修改操作分别在构造函数和析构函数中实现。
- 根据是否支持中断做不同实现,不需要中断则直接调用
Sleep
系统函数来实现。 - 如果要支持中断则接着做下面处理。
- ThreadBlockInVM 主要是检查当前线程用不用进入 safepoint,后面再详细看。
- 接着主要到
WaitForMultipleObjects
系统函数,该函数能等待指定对象指定的毫秒数。如果等待过程中对象没有接到任何信号,则超过指定毫秒数后返回WAIT_TIMEOUT
,如果等待过程中对象收到信号,则提前解除等待,此时返回的值为OS_INTRPT
,即表示被中断了。
int os::sleep(Thread* thread, jlong ms, bool interruptable) {
jlong limit = (jlong) MAXDWORD;
while (ms > limit) {
int res;
if ((res = sleep(thread, limit, interruptable)) != OS_TIMEOUT) {
return res;
}
ms -= limit;
}
assert(thread == Thread::current(), "thread consistency check");
OSThread* osthread = thread->osthread();
OSThreadWaitState osts(osthread, false /* not Object.wait() */);
int result;
if (interruptable) {
assert(thread->is_Java_thread(), "must be java thread");
JavaThread *jt = (JavaThread *) thread;
ThreadBlockInVM tbivm(jt);
jt->set_suspend_equivalent();
HANDLE events[1];
events[0] = osthread->interrupt_event();
HighResolutionInterval *phri=NULL;
if (!ForceTimeHighResolution) {
phri = new HighResolutionInterval(ms);
}
if (WaitForMultipleObjects(1, events, FALSE, (DWORD)ms) == WAIT_TIMEOUT) {
result = OS_TIMEOUT;
} else {
ResetEvent(osthread->interrupt_event());
osthread->set_interrupted(false);
result = OS_INTRPT;
}
delete phri;
jt->check_and_wait_while_suspended();
} else {
assert(!thread->is_Java_thread(), "must not be java thread");
Sleep((long) ms);
result = OS_TIMEOUT;
}
return result;
}
复制代码
ThreadBlockInVM
前面说到 ThreadBlockInVM 会检查当前线程用不用进入 safepoint,它主要的逻辑如下:
- 首先设置 Java 线程状态,将状态加一,由
_thread_in_vm = 6
变为_thread_in_vm_trans = 7
,从“运行vm本身代码”到“相应的过度状态”。 os::is_MP()
用于判断计算机系统是否为多核系统,多核情况下需要做内存屏障处理,这是为了让每个线程都能实时同步状态。- 内存屏障有两种方式,一种是
rderAccess::fence()
,它的实现是直接通过CPU指令来实现,汇编指令为__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
,这种方式代价比较大。而另外一种为InterfaceSupport::serialize_memory
,由 JVM 模拟实现,效率高一点。 - 调用
SafepointSynchronize::block
尝试在该安全点进行阻塞。 - 设置 Java 线程状态为
_thread_blocked
,即阻塞。
static inline void transition_and_fence(JavaThread *thread, JavaThreadState from, JavaThreadState to) {
assert(thread->thread_state() == from, "coming from wrong thread state");
assert((from & 1) == 0 && (to & 1) == 0, "odd numbers are transitions states");
thread->set_thread_state((JavaThreadState)(from + 1));
if (os::is_MP()) {
if (UseMembar) {
OrderAccess::fence();
} else {
// Must use this rather than serialization page in particular on Windows
InterfaceSupport::serialize_memory(thread);
}
}
if (SafepointSynchronize::do_call_back()) {
SafepointSynchronize::block(thread);
}
thread->set_thread_state(to);
CHECK_UNHANDLED_OOPS_ONLY(thread->clear_unhandled_oops();)
}
复制代码
-------------推荐阅读------------
我的开源项目汇总(机器&深度学习、NLP、网络IO、AIML、mysql协议、chatbot)
跟我交流,向我提问:
欢迎关注: