從Java到JVM到OS線程睡眠

做者:超人汪小建html

Java 中有時須要將線程進入睡眠狀態,這時通常咱們就會經過 Thread.sleep 使線程進入睡眠狀態,接下去就看看執行該語句在 JVM 中作了什麼。java

簡單例子安全

如下是一個簡單的例子,使主線程睡眠5秒鐘。app

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。oop

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 是一個靜態且本地方法。ui

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

Java 層聲明的本地方法對應實如今 Thread.c 中, sleep 是一個註冊到 JVM 中的方法,它與 JVM 中的 JVM_Sleep 函數綁定了,因此實現邏輯在 JVM_Sleep 函數裏。邏輯爲:spa

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,則作相似操做,不過它支持中斷。
發送事件,結束。

os::naked_yield

naked_yield 函數的實現很簡單,就直接調用 SwitchToThread 系統函數。經過該函數可讓系統查看是否有其餘線程迫切須要CPU,將CPU讓給其餘線程,若是沒有其餘線程則當即返回。

void os::naked_yield() {
  SwitchToThread();
}
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
  JVMWrapper("JVM_Sleep");

  if (millis 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::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指令來實現,彙編指令爲 asmvolatile ("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();)
  }

在這裏我整理了一些Java學習資料須要的能夠本身去領取

連接:http://yunxi.ai/java/java.html

相關文章
相關標籤/搜索