- Java多線程編程實戰(zhàn)指南:設計模式篇(第2版)
- 黃文海
- 1463字
- 2021-10-15 19:24:52
1.3 線程的狀態(tài)與上下文切換
在Java語言中,一個線程從創(chuàng)建、啟動到運行結(jié)束的整個生命周期可能經(jīng)歷若干個狀態(tài),如圖1-1所示。

圖1-1 Java線程的狀態(tài)
Java線程的狀態(tài)可以通過調(diào)用相應Thread實例的getState方法獲取。該方法的返回值類型Thread.State是一個枚舉類型(Enum)。Thread.State所定義的線程狀態(tài)包括以下幾種。
? NEW:一個剛創(chuàng)建而未啟動的線程處于該狀態(tài)。由于一個線程實例只能夠被啟動一次,因此一個線程只可能處于該狀態(tài)一次。
? RUNNABLE:該狀態(tài)可以看成一個復合狀態(tài)。它包括兩個子狀態(tài):READY和RUNNING。前者表示處于該狀態(tài)的線程可以被線程調(diào)度器(Scheduler)調(diào)度而使之處于RUNNING狀態(tài)。后者表示處于該狀態(tài)的線程正在運行,即相應線程對象的run方法中的代碼所對應的指令正在由CPU執(zhí)行。當Thread實例的yield方法被調(diào)用時或者通過線程調(diào)度器,相應線程的狀態(tài)會由RUNNING轉(zhuǎn)換為READY。
? BLOCKED:在一個線程發(fā)起一個阻塞式I/O(Blocking I/O)操作[5]后,或者試圖獲得一個由其他線程持有的鎖時,相應的線程會處于該狀態(tài)。處于該狀態(tài)的線程并不會占用CPU資源。在相應的I/O操作完成后,或者相應的鎖被其他線程釋放后,該線程的狀態(tài)又可以轉(zhuǎn)換為RUNNABLE。
? WAITING:一個線程在執(zhí)行了某些方法調(diào)用之后就會處于這種無限等待其他線程執(zhí)行特定操作的狀態(tài)。這些方法有Object.wait、Thread.join和LockSupport.park。能夠使相應線程從WAITING轉(zhuǎn)換到RUNNABLE的相應方法有Object.notify、Object.notifyAll和LockSupport.unpark(thread)。
? TIMED_WAITING:該狀態(tài)與WAITING狀態(tài)類似,差別在于處于該狀態(tài)的線程并非無限等待其他線程執(zhí)行特定操作,而是處于帶有時間限制的等待狀態(tài)。當其他線程沒有在指定時間內(nèi)執(zhí)行該線程所期望的特定操作時,該線程的狀態(tài)將自動轉(zhuǎn)換為RUNNABLE。
? TERMINATED:已經(jīng)執(zhí)行結(jié)束的線程處于該狀態(tài)。由于一個線程實例只能夠被啟動一次,因此一個線程也只可能處于該狀態(tài)一次。Thread實例的run方法正常返回或者由于拋出異常而提前停止,都會導致相應線程處于該狀態(tài)。
從上述描述可知,一個線程在其整個生命周期中,只可能處于NEW狀態(tài)和TERMINATED狀態(tài)一次。而一個線程的狀態(tài)從RUNNABLE狀態(tài)轉(zhuǎn)換為BLOCKED、WAITING和TIMED_WAITING等狀態(tài)中的任何一個狀態(tài),都意味著上下文切換(Context Switch)的產(chǎn)生。
上下文切換類似于我們接聽手機電話的場景。比如,當我們正在接聽一個電話并與對方討論某件事情的時候,這時突然有另一個來電。通常這個時候我們會跟對方說“我先接個電話,你別掛斷。”并記下與他的討論進行到了什么程度,然后接聽新的來電并告訴對方稍后會回撥并將該來電掛斷,接著又繼續(xù)先前的討論。在接聽新來電之前,如果我們沒有特意記下當前的討論進展到什么程度,等我們接聽新來電后再回過頭繼續(xù)討論時,可能得問對方“剛才我們講到哪里了”這樣的問題。
在多線程環(huán)境中,當一個線程的狀態(tài)由RUNNABLE轉(zhuǎn)換為非RUNNABLE(BLOCKED、WAITING或者TIMED_WAITING)時,相應線程的上下文信息(即所謂的Context,包括CPU的寄存器和程序計數(shù)器在某一時間點的內(nèi)容等)需要被保存,以便相應線程稍后再次進入RUNNABLE狀態(tài)時能夠在之前執(zhí)行進度的基礎上繼續(xù)前進。而當一個線程的狀態(tài)由非RUNNABLE狀態(tài)進入RUNNABLE狀態(tài)時,可能涉及恢復之前保存的線程上下文信息并在此基礎上前進。這個對線程的上下文信息進行保存和恢復的過程就被稱為上下文切換。
上下文切換會帶來額外的開銷,包括保存和恢復線程上下文信息的開銷、對線程進行調(diào)度的CPU時間開銷以及CPU緩存內(nèi)容失效(即CPU的L1 Cache、L2 Cache等)的開銷[6]。
在Linux平臺下,我們可以使用perf命令來監(jiān)視Java程序運行過程中的上下文切換情況。例如,我們可以使用perf命令來監(jiān)視如清單1-2所示程序的運行情況,相應的命令如下:

在上述命令中,參數(shù)e的值中的cs表示被監(jiān)視程序的上下文切換次數(shù)。上述命令執(zhí)行后輸出的內(nèi)容類似下面這樣:

由此可見,如清單1-2所示程序的這次運行一共產(chǎn)生了434次上下文切換。
在Windows平臺下,可以使用Windows自帶的工具perfmon[7]來監(jiān)視Java程序運行過程中的上下文切換情況。
- Extending Jenkins
- Flask Web全棧開發(fā)實戰(zhàn)
- 軟件架構(gòu)設計:大型網(wǎng)站技術架構(gòu)與業(yè)務架構(gòu)融合之道
- Windows系統(tǒng)管理與服務配置
- Machine Learning with R Cookbook(Second Edition)
- 跟老齊學Python:輕松入門
- Python爬蟲開發(fā)與項目實戰(zhàn)
- Python數(shù)據(jù)可視化之Matplotlib與Pyecharts實戰(zhàn)
- Android開發(fā)案例教程與項目實戰(zhàn)(在線實驗+在線自測)
- Building Android UIs with Custom Views
- Java零基礎實戰(zhàn)
- 區(qū)塊鏈項目開發(fā)指南
- C++ System Programming Cookbook
- Java自然語言處理(原書第2版)
- MySQL數(shù)據(jù)庫教程(視頻指導版)