Java中Thread.sleep源码分析

2023-03-15 13:53:22 浏览数 (2)

本文将从源码角度分析Thread.sleep的实现机制。OpenJDK版本

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

0 人点赞