從Java到JVM到OS線程睡眠

從Java到JVM到OS線程睡眠

鎮樓小姐姐

36份一線互聯網Java面試電子書

84個Java稀缺面試題視頻


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,則做類似操作,不過它支持中斷。

發送事件,結束。

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();)

}


分享到:


相關文章: