前言
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();)
- }
复制代码
来源: https://juejin.im/post/5b7a0bbbf265da437b082793