首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java中Thread.sleep源码分析

Java中Thread.sleep源码分析

作者头像
KINGYT
发布2023-03-15 13:53:22
7140
发布2023-03-15 13:53:22
举报
本文将从源码角度分析Thread.sleep的实现机制。OpenJDK版本

➜ jdk hg id 76072a077ee1+ jdk-11+28

首先看下sleep方法

public static native void sleep(long millis) throws InterruptedException;

该方法是个native方法,看下其对应的JVM内部的代码

// 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方法

// 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对应的类

// src/hotspot/share/runtime/park.hpp
class ParkEvent : public os::PlatformEvent {
  ...
} ;

该类继承了os::PlatformEvent类,继续看下这个类

// 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方法

// 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方法

// 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源码分析

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 卯时卯刻 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档