從Java到JVM到OS執行緒睡眠

超人汪小建發表於2019-02-24

前言

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_yieldos::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)

為什麼寫《Tomcat核心設計剖析》

我的2017文章彙總——機器學習篇

我的2017文章彙總——Java及中介軟體

我的2017文章彙總——深度學習篇

我的2017文章彙總——JDK原始碼篇

我的2017文章彙總——自然語言處理篇

我的2017文章彙總——Java併發篇


跟我交流,向我提問:

從Java到JVM到OS執行緒睡眠

歡迎關注:

從Java到JVM到OS執行緒睡眠

相關文章