Thread.sleep(0)的寫(xiě)法原理深入解析
前言
最近在網(wǎng)上看到了一段代碼,讓我感到很迷茫。他在代碼中使用了 Thread.sleep(0)
,讓線(xiàn)程休眠時(shí)間為0秒,具體代碼如下。
int i = 0; while (i<10000000) { // business logic //prevent long time gc if (i % 3000 == 0) { try { Thread.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); } } }
sleep
了0秒,不就是不睡覺(jué)嗎?我的第一反應(yīng)是這段代碼沒(méi)什么用,但是看到他的注釋又引起了我的興趣。經(jīng)過(guò)一番研究,看似無(wú)用的一段代碼,其實(shí)大有文章。
探索分析
為了找到原因,首先去看下sleep
方法的javadoc
,如下:
Causes the currently executing thread to sleep (temporarily ceaseexecution) for the specified number of milliseconds, subject tothe precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.
顯然沒(méi)有得到正確的答案,最后在詢(xún)問(wèn)作者說(shuō)是使用Thread.sleep(0)
可以暫時(shí)釋放CPU時(shí)間線(xiàn)。
時(shí)間片循環(huán)調(diào)度算法
在操作系統(tǒng)中,CPU有很多競(jìng)爭(zhēng)策略。Unix系統(tǒng)采用時(shí)間片循環(huán)調(diào)度算法。在該算法中,所有進(jìn)程都被分組到一個(gè)隊(duì)列中。操作系統(tǒng)按順序?yàn)槊總€(gè)進(jìn)程分配一定的時(shí)間,即允許進(jìn)程運(yùn)行的時(shí)間。如果在時(shí)間片結(jié)束時(shí)進(jìn)程仍在運(yùn)行,則CPU將被剝奪并分配給另一個(gè)進(jìn)程,如果進(jìn)程在時(shí)間片內(nèi)阻塞或結(jié)束,則CPU立即切換。調(diào)度程序所要做的就是維護(hù)一個(gè)就緒進(jìn)程表。當(dāng)進(jìn)程用完時(shí)間片時(shí),它將被移到隊(duì)列的末尾。
上面的代碼中存在死循環(huán)。作者希望一直用一個(gè)線(xiàn)程來(lái)處理業(yè)務(wù)邏輯。如果Thread.sleep(0)
不使用主動(dòng)放棄CPU時(shí)間片,線(xiàn)程資源會(huì)一直被占用。眾所周知,GC 線(xiàn)程具有低優(yōu)先級(jí),因此Thread.sleep(0)
用于幫助 GC 線(xiàn)程嘗試競(jìng)爭(zhēng) CPU 時(shí)間片。但是為什么作者說(shuō)可以防止long time GC
呢?這就講到JVM的垃圾回收原理了。
GC的安全點(diǎn)
以HotSpot
虛擬機(jī)為例,JVM并不會(huì)在代碼指令流的任何位置暫停以啟動(dòng)垃圾回收,而是強(qiáng)制執(zhí)行必須到達(dá)安全點(diǎn)才暫停。換句話(huà)說(shuō),在到達(dá)安全點(diǎn)之前,JVM 不會(huì)為 GC STOP THE WORLD
。
JVM 會(huì)在一些循環(huán)跳轉(zhuǎn)和方法調(diào)用上設(shè)置安全點(diǎn)。不過(guò),為了避免安全點(diǎn)過(guò)多帶來(lái)的沉重負(fù)擔(dān),HotSpot虛擬機(jī)還有一個(gè)針對(duì)循環(huán)的優(yōu)化措施。如果循環(huán)次數(shù)少,執(zhí)行時(shí)間不宜過(guò)長(zhǎng)。因此,默認(rèn)情況下不會(huì)將使用 int 或更小數(shù)據(jù)類(lèi)型作為索引值的循環(huán)放置在安全點(diǎn)中。這種循環(huán)稱(chēng)為可數(shù)循環(huán)。相應(yīng)地,使用long或更大范圍的數(shù)據(jù)類(lèi)型作為索引值的循環(huán)稱(chēng)為未計(jì)數(shù)循環(huán),將被放置在安全點(diǎn)。
但是,我們這里正好有一個(gè)可數(shù)循環(huán),所以我們的代碼不會(huì)放在安全點(diǎn)。因此,GC線(xiàn)程必須等到線(xiàn)程執(zhí)行完畢,才能執(zhí)行到最近的安全點(diǎn)。但如果使用Thread.sleep(0)
,則可以在代碼中放置一個(gè)安全點(diǎn)。我們可以看下HotSpot
的safepoint.cpp
源碼中的注釋?zhuān)龀苏f(shuō)明。
// Begin the process of bringing the system to a safepoint. // Java threads can be in several different states and are // stopped by different mechanisms: // // 1. Running interpreted // The interpeter dispatch table is changed to force it to // check for a safepoint condition between bytecodes. // 2. Running in native code // When returning from the native code, a Java thread must check // the safepoint _state to see if we must block. If the // VM thread sees a Java thread in native, it does // not wait for this thread to block. The order of the memory // writes and reads of both the safepoint state and the Java // threads state is critical. In order to guarantee that the // memory writes are serialized with respect to each other, // the VM thread issues a memory barrier instruction // (on MP systems). In order to avoid the overhead of issuing // a memory barrier for each Java thread making native calls, each Java // thread performs a write to a single memory page after changing // the thread state. The VM thread performs a sequence of // mprotect OS calls which forces all previous writes from all // Java threads to be serialized. This is done in the // os::serialize_thread_states() call. This has proven to be // much more efficient than executing a membar instruction // on every call to native code. // 3. Running compiled Code // Compiled code reads a global (Safepoint Polling) page that // is set to fault if we are trying to get to a safepoint. // 4. Blocked // A thread which is blocked will not be allowed to return from the // block condition until the safepoint operation is complete. // 5. In VM or Transitioning between states // If a Java thread is currently running in the VM or transitioning // between states, the safepointing code will wait for the thread to // block itself when it attempts transitions to a new state.
可以看上面的第2點(diǎn) Running in native code
,而Thread.sleep(long millis)
是一種native
方法。
總結(jié)
Thread.sleep(0)
不是什么無(wú)用的代碼。sleep
方法可用于在 java 代碼中放置一個(gè)安全點(diǎn)。可以提前在長(zhǎng)循環(huán)中觸發(fā)GC,避免GC線(xiàn)程長(zhǎng)時(shí)間等待,從而避免達(dá)到拉長(zhǎng)GC時(shí)間的目的。
以上就是Thread.sleep(0)的寫(xiě)法原理深入解析的詳細(xì)內(nèi)容,更多關(guān)于Thread.sleep(0)寫(xiě)法原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot之Duration(java.time.Duration)在yml properties中
這篇文章主要介紹了springboot之Duration(java.time.Duration)在yml properties中的配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12spring?controller層引用service報(bào)空指針異常nullpointExceptio問(wèn)題
這篇文章主要介紹了spring?controller層引用service報(bào)空指針異常nullpointExceptio問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02MyBatis實(shí)現(xiàn)Mysql數(shù)據(jù)庫(kù)分庫(kù)分表操作和總結(jié)(推薦)
這篇文章主要介紹了MyBatis實(shí)現(xiàn)Mysql數(shù)據(jù)庫(kù)分庫(kù)分表操作和總結(jié),需要的朋友可以參考下2017-08-08通過(guò)代碼示例了解submit與execute的區(qū)別
這篇文章主要介紹了通過(guò)代碼示例了解submit與execute的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09SpringBoot下如何實(shí)現(xiàn)支付寶接口的使用
這篇文章主要介紹了SpringBoot下如何實(shí)現(xiàn)支付寶接口的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11java線(xiàn)程的run()沒(méi)有返回值怎么辦?
java線(xiàn)程的run()沒(méi)有返回值怎么辦?本文給出了java線(xiàn)程的run()返回值為空的解決辦法,感興趣的小伙伴們可以參考一下2016-01-01Spring Cloud OAuth2 實(shí)現(xiàn)用戶(hù)認(rèn)證及單點(diǎn)登錄的示例代碼
這篇文章主要介紹了Spring Cloud OAuth2 實(shí)現(xiàn)用戶(hù)認(rèn)證及單點(diǎn)登錄的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10SpringBoot中HttpSessionListener的簡(jiǎn)單使用方式
這篇文章主要介紹了SpringBoot中HttpSessionListener的簡(jiǎn)單使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03