本文是關(guān)于實(shí)時(shí) Java™
系列文章(共 5 部分)的第三篇,考察了 Java 實(shí)時(shí)規(guī)范(RTSJ)的實(shí)現(xiàn)必須支持的線(xiàn)程化和同步問(wèn)題。您還將了解開(kāi)發(fā)和部署實(shí)時(shí)應(yīng)用程序時(shí)必須牢記的一些有關(guān)這兩方面的基本考慮。
線(xiàn)程化和同步是 Java 編程語(yǔ)言的核心特性,Java 語(yǔ)言規(guī)范(JLS)中對(duì)二者作出了描述。RTSJ 用多種方式擴(kuò)展了 JLS 的核心功能。(參見(jiàn) 參考資料 中關(guān)于 JLS 和 RTSJ 的鏈接。)例如,RTSJ 引入了一些新的實(shí)時(shí)(RT)線(xiàn)程類(lèi)型,它們必須遵守比普通 Java 線(xiàn)程更加嚴(yán)格的調(diào)度策略。另一個(gè)例子是優(yōu)先級(jí)繼承,它是一種鎖定策略,定義了鎖競(jìng)爭(zhēng)時(shí)如何管理鎖同步。
理解對(duì)優(yōu)先級(jí)和優(yōu)先級(jí)序列的管理有助于理解 RTSJ 針對(duì)線(xiàn)程化和同步所作的更改。優(yōu)先級(jí)也是 RT
應(yīng)用程序使用的一種重要工具。本文通過(guò)討論如何管理線(xiàn)程優(yōu)先級(jí)和優(yōu)先級(jí)序列來(lái)描述 RTSJ 線(xiàn)程化和同步。討論了開(kāi)發(fā)、部署和執(zhí)行 RT
應(yīng)用程序(包括使用 IBM WebSphere® Real Time 開(kāi)發(fā)的應(yīng)用程序,參見(jiàn) 參考資料)時(shí)應(yīng)該考慮的一些方面。
理解普通的 Java 線(xiàn)程
JLS 中定義的線(xiàn)程稱(chēng)為普通 Java 線(xiàn)程。普通 Java 線(xiàn)程是 java.lang.Thread 類(lèi)的一個(gè)實(shí)例,該類(lèi)擁有從 1 到 10 的 10 個(gè)優(yōu)先級(jí)別。為了適應(yīng)大量的執(zhí)行平臺(tái),JLS 在如何實(shí)現(xiàn)、調(diào)度和管理普通 Java 線(xiàn)程的優(yōu)先級(jí)方面提供了很大的靈活性。
WebSphere VMs on Linux®(包括 WebSphere Real Time)使用 Linux 操作系統(tǒng)提供的本地線(xiàn)程化服務(wù)。您可以通過(guò)理解 Linux 的線(xiàn)程化和同步來(lái)學(xué)習(xí) Java 的線(xiàn)程化和同步。
Linux 線(xiàn)程化和同步
Linux 操作系統(tǒng)發(fā)展至今已經(jīng)提供了不同用戶(hù)級(jí)別的線(xiàn)程化實(shí)現(xiàn)。Native POSIX Thread Library(NPTL)(參見(jiàn) 參考資料)是 Linux 最新版本的戰(zhàn)略性線(xiàn)程化實(shí)現(xiàn),由 WebSphere VMs 所使用。NPTL 與它的前任相比優(yōu)勢(shì)在于 POSIX 兼容性和性能。在編譯時(shí)可通過(guò)系統(tǒng)的頭文件獲取 POSIX 服務(wù)??稍谶\(yùn)行時(shí)通過(guò) libpthread.so 動(dòng)態(tài)庫(kù)和底層 Linux 核心支持獲取 POSIX 服務(wù)。Linux 核心可以根據(jù)靜態(tài)控制(如線(xiàn)程優(yōu)先級(jí)級(jí)別)和系統(tǒng)中執(zhí)行的線(xiàn)程的某些動(dòng)態(tài)條件下來(lái)執(zhí)行線(xiàn)程調(diào)度。
POSIX 允許您創(chuàng)建具有不同線(xiàn)程調(diào)度策略和優(yōu)先級(jí)的 POSIX 線(xiàn)程(pthreads)以滿(mǎn)足不同應(yīng)用程序的需求。下面是三種此類(lèi)的調(diào)度策略:
-
SCHED_OTHER
-
SCHED_FIFO
-
SCHED_RR
SCHED_OTHER 策略用于傳統(tǒng)用戶(hù)任務(wù),如程序開(kāi)發(fā)工具、辦公應(yīng)用程序和 Web 瀏覽器。 SCHED_RR 和 SCHED_FIFO 主要用于具有更高的確定性和時(shí)限需求的應(yīng)用程序。SCHED_RR 和 SCHED_FIFO 之間的主要區(qū)別是 SCHED_RR
分時(shí)間片 執(zhí)行線(xiàn)程,而 SCHED_FIFO 則不是這樣。SCHED_OTHER 和 SCHED_FIFO 策略用于 WebSphere Real Time,并在下面作出了更加詳細(xì)的描述。(我們不介紹 SCHED_RR 策略,WebSphere Real Time 沒(méi)有使用它。)
POSIX 通過(guò) pthread_mutex 數(shù)據(jù)類(lèi)型提供鎖定和同步支持。pthread_mutex 可以使用不同的鎖定策略創(chuàng)建。當(dāng)多個(gè)線(xiàn)程需要同時(shí)獲取同一個(gè)鎖的時(shí)候,鎖定策略常常會(huì)影響執(zhí)行行為。標(biāo)準(zhǔn)的 Linux 版本支持單個(gè)的默認(rèn)策略,而 RT Linux 版本還支持優(yōu)先級(jí)繼承鎖定策略。我們將在本文的 同步概述 一節(jié)對(duì)優(yōu)先級(jí)繼承策略作更詳細(xì)的描述。
Linux 調(diào)度和鎖定用來(lái)管理先進(jìn)先出(FIFO)隊(duì)列。
普通 Java 線(xiàn)程的線(xiàn)程調(diào)度
RTSJ 指出普通 Java 線(xiàn)程的行為跟 JLS 中定義的相同。在 WebSphere Real Time 中,普通 Java 線(xiàn)程使用 Linux 的 POSIX SCHED_OTHER 調(diào)度策略來(lái)實(shí)現(xiàn)。SCHED_OTHER 策略主要用于編譯器和字處理程序之類(lèi)的應(yīng)用程序,不能用于需要更高確定性的任務(wù)。
在 2.6 Linux 內(nèi)核中,SCHED_OTHER 策略支持 40 個(gè)優(yōu)先級(jí)級(jí)別。這 40 個(gè)優(yōu)先級(jí)級(jí)別基于處理器級(jí)別來(lái)管理,就是說(shuō):
-
出于緩存性能的原因,Linux 嘗試在同一個(gè)處理程序中執(zhí)行線(xiàn)程。
-
線(xiàn)程調(diào)度主要使用處理器級(jí)別的鎖而不是系統(tǒng)級(jí)別的鎖。
如有需要,Linux 可將線(xiàn)程從一個(gè)處理程序遷移到另一個(gè)處理程序以平衡工作量。
在(40 個(gè)中的)每個(gè)優(yōu)先級(jí)級(jí)別中,Linux 管理活動(dòng)隊(duì)列 和過(guò)期隊(duì)列。每個(gè)隊(duì)列包含一個(gè)線(xiàn)程鏈表(或者為空)。使用活動(dòng)和過(guò)期隊(duì)列出于以下目的:效率、負(fù)載平衡和其他一些目的。邏輯上可將系統(tǒng)看作:為(40 個(gè)中的)每個(gè)優(yōu)先級(jí)管理一個(gè) FIFO 序列,稱(chēng)為運(yùn)行隊(duì)列。一個(gè)從非空運(yùn)行隊(duì)列的前端分派的線(xiàn)程具有最高的優(yōu)先級(jí)。該線(xiàn)程從隊(duì)列中移除并執(zhí)行一段時(shí)間(稱(chēng)作:時(shí)間量 或時(shí)間片)。當(dāng)一個(gè)執(zhí)行線(xiàn)程超過(guò) 它的時(shí)間量時(shí),它的優(yōu)先級(jí)被放在運(yùn)行隊(duì)列的后端并給它指定了新的時(shí)間量。通過(guò)從隊(duì)列的前端分派線(xiàn)程和在隊(duì)列的后端放置過(guò)期的線(xiàn)程,程序在一個(gè)優(yōu)先級(jí)中輪替執(zhí)行。
為線(xiàn)程提供的時(shí)間量取決于給線(xiàn)程指定的優(yōu)先級(jí)。指定了較高優(yōu)先級(jí)的線(xiàn)程擁有較長(zhǎng)的執(zhí)行時(shí)間量。為了防止線(xiàn)程霸占 CPU,Linux 根據(jù)一些因素(如線(xiàn)程是 I/O 限制還是 CPU 限制)動(dòng)態(tài)提高或降低線(xiàn)程的優(yōu)先級(jí)。線(xiàn)程可以通過(guò)讓步(如調(diào)用 Thread.yield())自愿地放棄它的時(shí)間片,或通過(guò)阻塞放棄控制權(quán),在阻塞處等待事件發(fā)生。釋放鎖可以觸發(fā)一個(gè)這類(lèi)的事件。
WebSphere Real Time 中的 VM 沒(méi)有顯式地指定跨越 40 個(gè) SCHED_OTHER Linux 線(xiàn)程優(yōu)先級(jí)的 10 個(gè)普通 Java 線(xiàn)程優(yōu)先級(jí)。所有的普通 Java 線(xiàn)程,不論其 Java 優(yōu)先級(jí)如何,都被指定為默認(rèn)的 Linux 優(yōu)先級(jí)。默認(rèn)的 Linux 優(yōu)先級(jí)處于 40 個(gè) SCHED_OTHER
優(yōu)先級(jí)的中間位置。通過(guò)使用默認(rèn)優(yōu)先級(jí),普通 Java 線(xiàn)程可以順利地執(zhí)行,即不論 Linux 可能作出何種動(dòng)態(tài)優(yōu)先級(jí)調(diào)整,運(yùn)行隊(duì)列中的每個(gè)普通
Java 線(xiàn)程都能最終得到執(zhí)行。這里假設(shè)的是只執(zhí)行普通 Java 線(xiàn)程的系統(tǒng)而不是其他系統(tǒng),比如執(zhí)行 RT 線(xiàn)程的系統(tǒng)。
注意:WebSphere Real Time 中的 VM 和 WebSphere VM 的非 RT 版本都使用 SCHED_OTHER
策略并針對(duì)普通 Java 線(xiàn)程使用默認(rèn)優(yōu)先級(jí)指定。通過(guò)使用相同的策略,這兩種 JVM 具有相似但不相同的線(xiàn)程調(diào)度和同步特征。WebSphere
Real Time 類(lèi)庫(kù)中的更改、JVM 中的更改和為支持 RTSJ 而在 JIT 編譯器中作出的更改,以及 RT Metronome
垃圾收集器的引入(參見(jiàn) 參考資料)使應(yīng)用程序不可能在兩種虛擬機(jī)中以相同的同步和性能特征運(yùn)行。在 IBM WebSphere Real Time 測(cè)試期間,在測(cè)試程序中,同步差異使競(jìng)爭(zhēng)條件(換言之,bug)浮出了水面,而這些測(cè)試程序已經(jīng)在其他 JVM 上運(yùn)行了很多年。
使用普通 Java 線(xiàn)程的代碼示例
清單 1 展示了一個(gè)使用普通 Java 線(xiàn)程的程序,確定了兩個(gè)線(xiàn)程中的每一個(gè)在五秒的時(shí)間間隔內(nèi)在一個(gè)循環(huán)中執(zhí)行的迭代次數(shù):
清單 1. 普通 Java 線(xiàn)程
class myThreadClass extends java.lang.Thread {
volatile static boolean Stop = false;
// Primordial thread executes main()
public static void main(String args[]) throws InterruptedException {
// Create and start 2 threads
myThreadClass thread1 = new myThreadClass();
thread1.setPriority(4); // 1st thread at 4th non-RT priority
myThreadClass thread2 = new myThreadClass();
thread2.setPriority(6); // 2nd thread at 6th non-RT priority
thread1.start(); // start 1st thread to execute run()
thread2.start(); // start 2nd thread to execute run()
// Sleep for 5 seconds, then tell the threads to terminate
Thread.sleep(5*1000);
Stop = true;
}
public void run() { // Created threads execute this method
System.out.println("Created thread");
int count = 0;
for (;Stop != true;) { // continue until asked to stop
count++;
Thread.yield(); // yield to other thread
}
System.out.println("Thread terminates. Loop count is " + count);
}
}
|
清單 1 中的程序具有三個(gè)普通 Java 線(xiàn)程的用戶(hù)線(xiàn)程:
- 原始線(xiàn)程:
- 它是啟動(dòng)過(guò)程中隱式創(chuàng)建的主線(xiàn)程,執(zhí)行
main() 方法。
-
main() 創(chuàng)建了兩個(gè)普通 Java 線(xiàn)程:一個(gè)線(xiàn)程的優(yōu)先級(jí)為 4 而另一個(gè)線(xiàn)程的優(yōu)先級(jí)為 6。
- 主線(xiàn)程通過(guò)調(diào)用
Thread.sleep() 方法休眠五秒鐘來(lái)達(dá)到故意阻塞自身的目的。
- 休眠五秒鐘后,此線(xiàn)程指示其他兩個(gè)線(xiàn)程結(jié)束。
- 優(yōu)先級(jí)為 4 的線(xiàn)程:
- 此線(xiàn)程由原始線(xiàn)程創(chuàng)建,后者執(zhí)行包含
for 循環(huán)的 run() 方法。
- 該線(xiàn)程:
- 在每次循環(huán)迭代中增加一個(gè)計(jì)數(shù)。
- 通過(guò)調(diào)用
Thread.yield() 方法自愿放棄它的時(shí)間片。
- 在主線(xiàn)程發(fā)出請(qǐng)求時(shí)結(jié)束。結(jié)束前打印循環(huán)計(jì)數(shù)。
- 優(yōu)先級(jí)為 6 的線(xiàn)程:此線(xiàn)程執(zhí)行的動(dòng)作與優(yōu)先級(jí)為 4 的線(xiàn)程相同。
如果此程序在單處理器或卸載的多處理器上運(yùn)行,則每個(gè)線(xiàn)程打印的 for 循環(huán)迭代計(jì)數(shù)大致相同。在一次運(yùn)行中,程序?qū)⒋蛴。?
Created thread
Created thread
Thread terminates. Loop count is 540084
Thread terminates. Loop count is 540083
|
如果刪除對(duì) Thread.yield() 的調(diào)用,則兩個(gè)線(xiàn)程的循環(huán)計(jì)數(shù)可能相近,但絕不可能相同。在 SCHED_OTHER
策略中為這兩個(gè)線(xiàn)程都指定了相同的默認(rèn)優(yōu)先級(jí)。因此給兩個(gè)線(xiàn)程分配了相同的時(shí)間片執(zhí)行。因?yàn)榫€(xiàn)程執(zhí)行的是相同的代碼,所以它們應(yīng)作出類(lèi)似的動(dòng)態(tài)優(yōu)先級(jí)調(diào)整
并在相同的運(yùn)行隊(duì)列中輪替執(zhí)行。但是由于首先執(zhí)行優(yōu)先級(jí)為 4
的線(xiàn)程,因此在五秒鐘的執(zhí)行時(shí)間間隔中,它分得的時(shí)間稍多一些并且打印的循環(huán)計(jì)數(shù)也稍大一些。
理解 RT 線(xiàn)程
RT 線(xiàn)程是 javax.realtime.RealtimeThread 的一個(gè)實(shí)例。RTSJ 要求規(guī)范的實(shí)現(xiàn)必須為 RT 線(xiàn)程提供至少 28 個(gè)連續(xù)的優(yōu)先級(jí)。這些優(yōu)先級(jí)被稱(chēng)作實(shí)時(shí)優(yōu)先級(jí)。規(guī)范中并沒(méi)有指定 RT 優(yōu)先級(jí)范圍的開(kāi)始值,除非其優(yōu)先級(jí)高于 10 —— 普通 Java 線(xiàn)程的最高優(yōu)先級(jí)值。出于可移植性的原因,應(yīng)用程序代碼應(yīng)使用新的 PriorityScheduler 類(lèi)的 getPriorityMin() 和 getPriorityMax() 方法來(lái)確定可用的 RT 優(yōu)先級(jí)值的范圍。
對(duì) RT 線(xiàn)程的推動(dòng)
JLS 中的線(xiàn)程調(diào)度并不精確而且只提供了 10 個(gè)優(yōu)先級(jí)值。由 Linux 實(shí)現(xiàn)的 POSIX SCHED_OTHER 策略滿(mǎn)足了各種應(yīng)用程序的需要。但是 SCHED_OTHER 策略具有一些不好的特性。動(dòng)態(tài)優(yōu)先級(jí)調(diào)整和時(shí)間片劃分可能在不可預(yù)測(cè)的時(shí)間內(nèi)發(fā)生。SCHED_OTHER 優(yōu)先級(jí)的值(40)其實(shí)并不算大,其中一部分已經(jīng)被使用普通 Java 線(xiàn)程的應(yīng)用程序和動(dòng)態(tài)優(yōu)先級(jí)調(diào)整利用了。JVM 還需要對(duì)內(nèi)部線(xiàn)程使用優(yōu)先級(jí)以達(dá)到一些特殊目的,比如垃圾收集(GC)。
缺少確定性、需要更高的優(yōu)先級(jí)級(jí)別以及要求與現(xiàn)有應(yīng)用程序兼容,這些因素引發(fā)了對(duì)擴(kuò)展的需求,這將為 Java 程序員提供新的調(diào)度功能。RTSJ 中描述的 javax.realtime 包中的類(lèi)提供了這些功能。在 WebSphere Real Time 中,Linux SCHED_FIFO 調(diào)度策略滿(mǎn)足了 RTSJ 調(diào)度需求。
RT Java 線(xiàn)程的線(xiàn)程調(diào)度
在 WebSphere Real Time 中,支持 28 個(gè) RT Java 優(yōu)先級(jí),其范圍為 11 到 38。PriorityScheduler 類(lèi)的 API 應(yīng)用于檢索這個(gè)范圍。本節(jié)描述了比 RTSJ 更多的線(xiàn)程調(diào)度細(xì)節(jié)以及 Linux SCHED_FIFO 策略的一些方面,已經(jīng)超出了 RTSJ 的需求。
RTSJ 將 RT 優(yōu)先級(jí)視作由運(yùn)行時(shí)系統(tǒng)在邏輯上實(shí)現(xiàn)的優(yōu)先級(jí),該系統(tǒng)為每個(gè) RT
優(yōu)先級(jí)保持一個(gè)獨(dú)立隊(duì)列。線(xiàn)程調(diào)度程序必須從非空的最高優(yōu)先級(jí)隊(duì)列的頭部開(kāi)始調(diào)度。注意:如果所有隊(duì)列中的線(xiàn)程都不具有 RT 優(yōu)先級(jí),則調(diào)度一個(gè)普通
Java 線(xiàn)程按 JLS 中的描述執(zhí)行(參見(jiàn) 普通 Java 線(xiàn)程的線(xiàn)程調(diào)度)。
具有 RT 優(yōu)先級(jí)的調(diào)度線(xiàn)程可以一直執(zhí)行直至阻塞,通過(guò)讓步自愿放棄控制權(quán),或被具有更高 RT 優(yōu)先級(jí)的線(xiàn)程搶占。具有 RT
優(yōu)先級(jí)并自愿讓步的線(xiàn)程的優(yōu)先級(jí)被置于隊(duì)列的后端。RTSJ 還要求此類(lèi)調(diào)度在不變的時(shí)間內(nèi)進(jìn)行,并且不能隨某些因素變化(如當(dāng)前執(zhí)行的 RT
線(xiàn)程的數(shù)量)。RTSJ 的 1.02 版本對(duì)單處理器系統(tǒng)應(yīng)用了這些規(guī)則;RTSJ 對(duì)于多處理器系統(tǒng)上的調(diào)度如何運(yùn)作未作要求。
Linux 為所有適當(dāng)?shù)?RTSJ 調(diào)度需求提供了 SCHED_FIFO 策略。SCHED_FIFO 策略用于 RT 而不用于用戶(hù)任務(wù)。SCHED_FIFO 與 SCHED_OTHER 策略的區(qū)別在于前者提供了 99 個(gè)優(yōu)先級(jí)級(jí)別。SCHED_FIFO 不為線(xiàn)程分時(shí)間片。同樣,SCHED_FIFO 策略也不動(dòng)態(tài)調(diào)整 RT 線(xiàn)程的優(yōu)先級(jí),除非通過(guò)優(yōu)先級(jí)繼承鎖定策略(同步概述 一節(jié)對(duì)此作出了描述)。由于優(yōu)先級(jí)繼承的原因,RTSJ 需要使用優(yōu)先級(jí)調(diào)整。
Linux 為 RT 線(xiàn)程和普通 Java 線(xiàn)程提供不變時(shí)間調(diào)度。在多處理器系統(tǒng)中,Linux 試圖模擬分派到可用處理器的單個(gè)全局 RT 線(xiàn)程隊(duì)列的行為。這與 RTSJ 的精神最為接近,但確實(shí)與用于普通 Java 線(xiàn)程的 SCHED_OTHER 策略不同。
使用 RT 線(xiàn)程的有問(wèn)題的代碼示例
清單 2 修改 清單 1 中的代碼來(lái)創(chuàng)建 RT 線(xiàn)程而不是普通 Java 線(xiàn)程。使用 java.realtime.RealtimeThread 而不是 java.lang.Thread 指出了其中的區(qū)別。第一個(gè)線(xiàn)程創(chuàng)建于第 4 RT 優(yōu)先級(jí)而第二個(gè)線(xiàn)程創(chuàng)建于第 6 RT 優(yōu)先級(jí),與 getPriorityMin() 方法確定的相同。
清單 2. RT 線(xiàn)程
import javax.realtime.*;
class myRealtimeThreadClass extends javax.realtime.RealtimeThread {
volatile static boolean Stop = false;
// Primordial thread executes main()
public static void main(String args[]) throws InterruptedException {
// Create and start 2 threads
myRealtimeThreadClass thread1 = new myRealtimeThreadClass();
// want 1st thread at 4th real-time priority
thread1.setPriority(PriorityScheduler.getMinPriority(null)+ 4);
myRealtimeThreadClass thread2 = new myRealtimeThreadClass();
// want 2nd thread at 6th real-time priority
thread2.setPriority(PriorityScheduler.getMinPriority(null)+ 6);
thread1.start(); // start 1st thread to execute run()
thread2.start(); // start 2nd thread to execute run()
// Sleep for 5 seconds, then tell the threads to terminate
Thread.sleep(5*1000);
Stop = true;
}
public void run() { // Created threads execute this method
System.out.println("Created thread");
int count = 0;
for (;Stop != true;) { // continue until asked to stop
count++;
// Thread.yield(); // yield to other thread
}
System.out.println("Thread terminates. Loop count is " + count);
}
}
|
清單 2 中修改后的代碼存在一些問(wèn)題。
如果程序在單處理器環(huán)境中運(yùn)行,則它永遠(yuǎn)不會(huì)結(jié)束并且只能打印以下內(nèi)容:
出現(xiàn)這樣的結(jié)果可以用 RT 線(xiàn)程調(diào)度的行為來(lái)解釋。原始線(xiàn)程仍然是一個(gè)普通 Java 線(xiàn)程并利用非 RT(SCHED_OTHER)策略運(yùn)行。只要原始線(xiàn)程啟動(dòng)第一個(gè) RT 線(xiàn)程,RT 線(xiàn)程就搶占原始線(xiàn)程并且 RT 線(xiàn)程將會(huì)不確定地運(yùn)行,因?yàn)樗皇軙r(shí)間量和線(xiàn)程阻塞的限制。原始線(xiàn)程被搶占后,就再也不允許執(zhí)行,因此再也不會(huì)啟動(dòng)第二個(gè) RT 線(xiàn)程。Thread.yield() 對(duì)允許原始線(xiàn)程執(zhí)行反而不起作用 —— 因?yàn)樽尣竭壿媽?RT 線(xiàn)程置于其運(yùn)行隊(duì)列的末端 —— 但是線(xiàn)程調(diào)度程序?qū)⒃俅握{(diào)度這個(gè)線(xiàn)程,因?yàn)樗沁\(yùn)行隊(duì)列前端的具有最高優(yōu)先級(jí)的線(xiàn)程。
該程序在雙處理器系統(tǒng)中同樣會(huì)失敗。它將打印以下內(nèi)容:
Created thread
Created thread
|
允許使用原始線(xiàn)程創(chuàng)建這兩個(gè) RT 線(xiàn)程。但是創(chuàng)建第二個(gè)線(xiàn)程后,原始線(xiàn)程被搶占并且再也不允許告知線(xiàn)程結(jié)束,因?yàn)閮蓚€(gè) RT 線(xiàn)程在兩個(gè)處理器上執(zhí)行而且永遠(yuǎn)不會(huì)阻塞。
在帶有三個(gè)或更多處理器的系統(tǒng)上,程序運(yùn)行至完成并生成一個(gè)結(jié)果。
單處理器上運(yùn)行的 RT 代碼示例
清單 3 顯示了修改后能在單處理器系統(tǒng)中正確運(yùn)行的代碼。main()
方法的邏輯被移到了一個(gè)具有第 8 RT 優(yōu)先級(jí)的 “main” RT 線(xiàn)程中。這個(gè)優(yōu)先級(jí)比主 RT 線(xiàn)程創(chuàng)建的兩個(gè)其他 RT
線(xiàn)程的優(yōu)先級(jí)都要高。擁有最高的 RT 優(yōu)先級(jí)使這個(gè)主 RT 線(xiàn)程能夠成功地創(chuàng)建兩個(gè) RT
線(xiàn)程,并且還允許它從五秒鐘的休眠中蘇醒時(shí)能夠搶占當(dāng)前運(yùn)行的線(xiàn)程。
清單 3. 修改后的 RT 線(xiàn)程示例
import javax.realtime.*;
class myRealtimeThreadClass extends javax.realtime.RealtimeThread {
volatile static boolean Stop = false;
static class myRealtimeStartup extends javax.realtime.RealtimeThread {
public void run() {
// Create and start 2 threads
myRealtimeThreadClass thread1 = new myRealtimeThreadClass();
// want 1st thread at 4th real-time priority
thread1.setPriority(PriorityScheduler.getMinPriority(null)+ 4);
myRealtimeThreadClass thread2 = new myRealtimeThreadClass();
// want 1st thread at 6th real-time priority
thread2.setPriority(PriorityScheduler.getMinPriority(null)+ 6);
thread1.start(); // start 1st thread to execute run()
thread2.start(); // start 2nd thread to execute run()
// Sleep for 5 seconds, then tell the threads to terminate
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
}
myRealtimeThreadClass.Stop = true;
}
}
// Primordial thread creates real-time startup thread
public static void main(String args[]) {
myRealtimeStartup startThr = new myRealtimeStartup();
startThr.setPriority(PriorityScheduler.getMinPriority(null)+ 8);
startThr.start();
}
public void run() { // Created threads execute this method
System.out.println("Created thread");
int count = 0;
for (;Stop != true;) { // continue until asked to stop
count++;
// Thread.yield(); // yield to other thread
}
System.out.println("Thread terminates. Loop count is " + count);
}
}
|
當(dāng)此程序在單處理器上運(yùn)行時(shí),它將打印以下結(jié)果:
Created thread
Thread terminates. Loop count is 32767955
Created thread
Thread terminates. Loop count is 0
|
程序的輸出顯示所有的線(xiàn)程運(yùn)行并結(jié)束,但是這兩個(gè)線(xiàn)程只有一個(gè)執(zhí)行 for 循環(huán)的一個(gè)迭代。這個(gè)輸出可通過(guò)考慮 RT 線(xiàn)程的優(yōu)先級(jí)來(lái)解釋。主 RT 線(xiàn)程一直運(yùn)行,直至調(diào)用 Thread.sleep()
方法來(lái)阻塞線(xiàn)程。主 RT 線(xiàn)程創(chuàng)建了兩個(gè) RT 線(xiàn)程,但是只有第二個(gè) RT 線(xiàn)程(具有第 6 RT 優(yōu)先級(jí))才能夠在主 RT
線(xiàn)程休眠時(shí)運(yùn)行。這個(gè)線(xiàn)程一直運(yùn)行,直至主 RT 線(xiàn)程從休眠中蘇醒并指示線(xiàn)程結(jié)束。主 RT 線(xiàn)程一旦結(jié)束,就允許執(zhí)行具有第 6
優(yōu)先級(jí)的線(xiàn)程并結(jié)束。程序按這種方式執(zhí)行并打印具有非零值循環(huán)計(jì)數(shù)。此線(xiàn)程結(jié)束后,就允許運(yùn)行具有第 4 RT 優(yōu)先級(jí)的線(xiàn)程,但它只是繞過(guò) for 循環(huán),因?yàn)橄到y(tǒng)指示結(jié)束該線(xiàn)程。該線(xiàn)程將打印零循環(huán)計(jì)數(shù)值然后結(jié)束。
RT 應(yīng)用程序的線(xiàn)程化考慮
移植應(yīng)用程序以使用 RT 線(xiàn)程或編寫(xiě)新應(yīng)用程序以利用 RT 線(xiàn)程化時(shí)需要考慮 RT 線(xiàn)程化的一些特性,本節(jié)將討論這些特性。
RT 線(xiàn)程的新擴(kuò)展
RTSJ 指定了一些工具,用于創(chuàng)建在某個(gè)特定或相關(guān)時(shí)間啟動(dòng)的 RT 線(xiàn)程。您可以創(chuàng)建一個(gè)線(xiàn)程,用于在指定的時(shí)間間隔或時(shí)期內(nèi)運(yùn)行某種邏輯。您可以定義一個(gè)線(xiàn)程,用于未在指定時(shí)期內(nèi)完成此邏輯時(shí)執(zhí)行(激發(fā))一個(gè) AsynchronousEventHandler(AEH)。您還可以定義線(xiàn)程所能夠使用的內(nèi)存類(lèi)型和數(shù)量的限制,如果超過(guò)該限制,則拋出 OutOfMemoryError。這些工具只對(duì) RT 線(xiàn)程可用,而對(duì)普通 Java 線(xiàn)程不可用。您可以在 RTSJ 中找到關(guān)于這些工具的更多信息。
Thread.interrupt() 和 pending 異常
RT 線(xiàn)程擴(kuò)展了 Thread.interrupt() 行為。此 API 會(huì)像 JLS 中描述的那樣中斷被阻塞的進(jìn)程。如果用戶(hù)在方法聲明中加入 Throws AsynchronouslyInterruptedException 子句,顯式地將其標(biāo)記為可中斷,也會(huì)引起這個(gè)異常。該異常也會(huì)困擾 線(xiàn)程,用戶(hù)必須顯式地清除異常;否則它會(huì)一直困擾(術(shù)語(yǔ)為 pending)線(xiàn)程。如果用戶(hù)不清除異常,則線(xiàn)程會(huì)伴隨著該異常而結(jié)束。如果線(xiàn)程以 “常規(guī)” 形式結(jié)束,但是不是在按自身形式進(jìn)行 RT 線(xiàn)程入池的應(yīng)用程序中,這種錯(cuò)誤危害不大,就是說(shuō),線(xiàn)程返回池中時(shí)仍然隨附 InterruptedException。在這種情況下,執(zhí)行線(xiàn)程入池的代碼應(yīng)顯式地清除異常;否則,當(dāng)重新分配具有隨附異常的入池線(xiàn)程時(shí),可能欺騙性地拋出異常。
原始線(xiàn)程和應(yīng)用程序調(diào)度邏輯
原始線(xiàn)程通常都是普通 Java 線(xiàn)程 —— 而不是 RT 線(xiàn)程。第一個(gè) RT 線(xiàn)程總是由普通 Java
線(xiàn)程創(chuàng)建。如果沒(méi)有足夠的可用處理器來(lái)同時(shí)運(yùn)行 RT 線(xiàn)程和普通 Java 線(xiàn)程,則這個(gè) RT 線(xiàn)程會(huì)立即搶占普通 Java
線(xiàn)程。搶占可以防止普通 Java 線(xiàn)程繼續(xù)創(chuàng)建 RT 線(xiàn)程或其他邏輯,以便將應(yīng)用程序置于適當(dāng)?shù)某跏蓟癄顟B(tài)。
您
可以通過(guò)從一個(gè)高優(yōu)先級(jí) RT
線(xiàn)程執(zhí)行應(yīng)用程序初始化來(lái)避免這個(gè)問(wèn)題。執(zhí)行自身形式的線(xiàn)程入池和線(xiàn)程調(diào)度的應(yīng)用程序或庫(kù)可能需要這種技術(shù)。即,線(xiàn)程調(diào)度邏輯應(yīng)該以高優(yōu)先級(jí)運(yùn)行,或在高
優(yōu)先級(jí)的線(xiàn)程中運(yùn)行。為執(zhí)行線(xiàn)程入池邏輯選擇適當(dāng)?shù)膬?yōu)先級(jí)有助于防止線(xiàn)程入隊(duì)和出隊(duì)中遇到的問(wèn)題。
失控線(xiàn)程
普
通 Java 線(xiàn)程按時(shí)間量執(zhí)行,而動(dòng)態(tài)優(yōu)先級(jí)根據(jù) CPU 的使用調(diào)整調(diào)度程序的執(zhí)行,允許所有的普通 Java 線(xiàn)程最后執(zhí)行。反過(guò)來(lái),RT
線(xiàn)程不受時(shí)間量的限制,并且線(xiàn)程調(diào)度程序不根據(jù) CPU 的使用進(jìn)行任何形式的動(dòng)態(tài)優(yōu)先級(jí)調(diào)整。普通 Java 線(xiàn)程和 RT
線(xiàn)程之間的調(diào)度策略差異使失控 RT 線(xiàn)程的出現(xiàn)成為可能。失控 RT 線(xiàn)程可以控制系統(tǒng)并阻止所有其他應(yīng)用程序的運(yùn)行,阻止用戶(hù)登錄系統(tǒng)等等。
在開(kāi)發(fā)和測(cè)試期間,有一種技術(shù)可以幫助減輕失控線(xiàn)程的影響,即限制進(jìn)程能夠使用的 CPU 數(shù)量。在
Linux 上,限制 CPU 的使用使進(jìn)程在耗盡 CPU 限制時(shí)終止失控線(xiàn)程。另外,監(jiān)控系統(tǒng)狀態(tài)或提供系統(tǒng)登錄的程序應(yīng)該以高 RT
優(yōu)先級(jí)運(yùn)行,以便程序可以搶占問(wèn)題線(xiàn)程。
從 Java 優(yōu)先級(jí)到操作系統(tǒng)優(yōu)先級(jí)的映射
在 Linux 上,POSIX SCHED_FIFO
策略提供了從 1 到 99 的整數(shù)范圍內(nèi)的 99 個(gè) RT 優(yōu)先級(jí)。在這個(gè)系統(tǒng)范圍內(nèi),從 11 到 89 的優(yōu)先級(jí)由 WebSphere VM
使用,此范圍的一個(gè)子集用來(lái)實(shí)現(xiàn) 28 個(gè) RTSJ 優(yōu)先級(jí)。28 個(gè) RT Java 優(yōu)先級(jí)映射到此范圍的 POSIX 系統(tǒng)優(yōu)先級(jí),IBM
WebSphere Real Time 文檔中對(duì)這一點(diǎn)作出了描述。但是應(yīng)用程序代碼不應(yīng)該依賴(lài)這個(gè)映射,而只應(yīng)該依賴(lài)于 Java 級(jí)別的 28
個(gè) RT 優(yōu)先級(jí)的相關(guān)順序。這樣 JVM 可以在未來(lái)的 WebSphere Real Time 版本中重新映射這個(gè)范圍并提供改進(jìn)。
如果某些端口監(jiān)督程序或 RT 進(jìn)程需要的優(yōu)先級(jí)高于或低于 WebSphere Real Time 中使用的優(yōu)先級(jí),則應(yīng)用程序可以使用 SCHED_FIFO 優(yōu)先級(jí) 1 或優(yōu)先級(jí) 90 來(lái)實(shí)現(xiàn)這些程序或進(jìn)程。
JNI AttachThread()
Java Native Interface (JNI) 允許使用 JNI AttachThread()
API 將使用 C 代碼創(chuàng)建的線(xiàn)程加入到 JVM 中,但 RTSJ 并不對(duì) JNI 接口進(jìn)行更改或配置以便加入 RT
線(xiàn)程。因此,應(yīng)用程序應(yīng)避免用 C 代碼創(chuàng)建準(zhǔn)備加入到 JVM 中的 POSIX RT 線(xiàn)程。反過(guò)來(lái),應(yīng)該在 Java 語(yǔ)言中創(chuàng)建此類(lèi) RT
線(xiàn)程。
派生進(jìn)程和 RT 優(yōu)先級(jí)
一
個(gè)線(xiàn)程可以派生另一個(gè)進(jìn)程。在 Linux 上,派生進(jìn)程的原始線(xiàn)程繼承派生它的父線(xiàn)程的優(yōu)先級(jí)。如果派生進(jìn)程是一個(gè) JVM,則 JVM
的原始線(xiàn)程創(chuàng)建時(shí)具有 RT 優(yōu)先級(jí)。這將與普通 Java 線(xiàn)程的順序沖突,比如原始線(xiàn)程的調(diào)度優(yōu)先級(jí)比 RT 線(xiàn)程低。為了防止這種情形,JVM
強(qiáng)制原始線(xiàn)程擁有非 RT 優(yōu)先級(jí) —— 即擁有 SCHED_OTHER 策略。
Thread.yield()
Thread.yield() 只讓步給具有相同優(yōu)先級(jí)的線(xiàn)程,決不會(huì)讓步給高于或低于自身優(yōu)先級(jí)的線(xiàn)程。只讓步給具有相同優(yōu)先級(jí)的線(xiàn)程意味著在使用多個(gè) RT 優(yōu)先級(jí)的 RT 應(yīng)用程序中使用 Thread.yield() 可能會(huì)出現(xiàn)問(wèn)題。應(yīng)該避免使用 Thread.yield(),除非完全有必要。
NoHeapRealtimeThreads
javax.realtime.NoHeapRealtimeThread (NHRT) 是 RTSJ 中的另一種新的線(xiàn)程類(lèi)型,它是 javax.realtime.RealtimeThread 的一個(gè)子類(lèi)。NHRT 具有與我們所描述的 RT 線(xiàn)程相同的調(diào)度特征,只是 NHRT 不會(huì)被 GC 搶占并且 NHRT 無(wú)法讀或?qū)?Java 堆。NHRT 是 RTSJ 的一個(gè)重要方面,本系列的后續(xù)文章中將對(duì)它進(jìn)行討論。
AsynchronousEventHandlers
AsynchronousEventHandler (AEH) 是 RTSJ 附帶的新增程序,可將它視為發(fā)生事件時(shí)執(zhí)行的一種 RT 線(xiàn)程。例如,可以設(shè)置 AEH 在某個(gè)特定或關(guān)聯(lián)時(shí)間激發(fā)。AEH 還具有與 RT 線(xiàn)程相同的調(diào)度特征并具有堆和非堆兩種風(fēng)格。
同步概述
許多 Java 應(yīng)用程序直接使用 Java 線(xiàn)程化特性,或正在開(kāi)發(fā)中的應(yīng)用程序使用涉及多個(gè)線(xiàn)程的庫(kù)。多線(xiàn)程編程中的一個(gè)主要考慮是確保程序在執(zhí)行多線(xiàn)程的系統(tǒng)中正確地 —— 線(xiàn)程安全地 —— 運(yùn)行。要保證程序線(xiàn)程安全地運(yùn)行,需要序列化訪問(wèn)由多個(gè)使用同步原語(yǔ)(如鎖或原子機(jī)器操作)的線(xiàn)程共享的數(shù)據(jù)。RT 應(yīng)用程序的編程人員通常面臨使程序按某種時(shí)間約束執(zhí)行的挑戰(zhàn)。為了應(yīng)對(duì)這個(gè)挑戰(zhàn),他們可能需要了解當(dāng)前使用組件的實(shí)現(xiàn)細(xì)節(jié)、含意和性能屬性。
本文的剩余部分將討論 Java 語(yǔ)言提供的核心同步原語(yǔ)的各個(gè)方面,這些原語(yǔ)在 RTSJ 中如何更改,以及 RT 編程人員使用這些原語(yǔ)時(shí)需要注意的一些暗示。
Java 語(yǔ)言同步概述
Java 語(yǔ)言提供了三種核心同步原語(yǔ):
-
同步的方法和代碼塊允許線(xiàn)程在入口處鎖定對(duì)象并在出口處解鎖(針對(duì)方法或代碼塊)。
-
Object.wait() 釋放對(duì)象鎖,線(xiàn)程等待。
-
Object.notify() 為 wait() 對(duì)象的線(xiàn)程解鎖。notifyAll() 為所有等待的線(xiàn)程解鎖。
執(zhí)行 wait() 和 notify() 的線(xiàn)程當(dāng)前必須已經(jīng)鎖定對(duì)象。
當(dāng)線(xiàn)程試圖鎖定的對(duì)象已被其他線(xiàn)程鎖定時(shí)將發(fā)生鎖爭(zhēng)用。當(dāng)發(fā)生這種情況時(shí),沒(méi)有獲得鎖的線(xiàn)程被置于對(duì)象的鎖爭(zhēng)用者的一個(gè)邏輯隊(duì)列中。類(lèi)似地,幾個(gè)線(xiàn)程可能對(duì)同一個(gè)對(duì)象執(zhí)行 Object.wait(),因此該對(duì)象擁有一個(gè)等待者的邏輯隊(duì)列。JLS 沒(méi)有指定如何管理這些隊(duì)列,但是 RTSJ 規(guī)定了這個(gè)行為。
基于優(yōu)先級(jí)的同步隊(duì)列
RTSJ
的原理是所有的線(xiàn)程隊(duì)列都是 FIFO 并且是基于優(yōu)先級(jí)的?;趦?yōu)先級(jí)的 FIFO 行為 ——
在前面的同步示例中,將接著執(zhí)行具有最高優(yōu)先級(jí)的線(xiàn)程 —— 也適用于鎖爭(zhēng)用者和鎖等待者的隊(duì)列。從邏輯觀點(diǎn)來(lái)看,鎖爭(zhēng)用者的 FIFO
基于優(yōu)先級(jí)的隊(duì)列與等待執(zhí)行的線(xiàn)程執(zhí)行隊(duì)列相似。同樣有相似的鎖等待者隊(duì)列。
釋放鎖以后,系統(tǒng)從爭(zhēng)用者的最高優(yōu)先級(jí)隊(duì)列的前端選擇線(xiàn)程,以便試圖鎖定對(duì)象。類(lèi)似地,完成 notify() 以后,等待者的最高優(yōu)先級(jí)隊(duì)列前端的線(xiàn)程從等待中解除阻塞。鎖釋放或鎖 notify() 操作與調(diào)度分派操作類(lèi)似,因?yàn)槎际菍?duì)最高優(yōu)先級(jí)隊(duì)列頭部的線(xiàn)程起作用。
為了支持基于優(yōu)先級(jí)的同步,需要對(duì) RT Linux 作一些修改。還需要對(duì) WebSphere Real Time 中的 VM 作出更改,以便在執(zhí)行 notify() 操作時(shí)委托 Linux 選擇對(duì)哪一個(gè)線(xiàn)程解除阻塞。
優(yōu)先級(jí)反轉(zhuǎn)和優(yōu)先級(jí)繼承
優(yōu)先級(jí)反轉(zhuǎn) 指的是阻塞高優(yōu)先級(jí)線(xiàn)程的鎖由低優(yōu)先級(jí)線(xiàn)程持有。中等優(yōu)先級(jí)線(xiàn)程可能搶占低優(yōu)先級(jí)線(xiàn)程,同時(shí)持有鎖并優(yōu)先于低優(yōu)先級(jí)線(xiàn)程運(yùn)行。優(yōu)先級(jí)反轉(zhuǎn)將延遲低優(yōu)先級(jí)線(xiàn)程和高優(yōu)先級(jí)線(xiàn)程的執(zhí)行。優(yōu)先級(jí)反轉(zhuǎn)導(dǎo)致的延遲可能導(dǎo)致無(wú)法滿(mǎn)足關(guān)鍵的時(shí)限。圖 1 的第一條時(shí)間線(xiàn)顯示這種情況。
優(yōu)先級(jí)繼承
是一種用于避免優(yōu)先級(jí)反轉(zhuǎn)的技術(shù)。優(yōu)先級(jí)繼承由 RTSJ
規(guī)定。優(yōu)先級(jí)繼承背后的思想是鎖爭(zhēng)用,鎖持有者的優(yōu)先級(jí)被提高到希望獲取鎖的線(xiàn)程的優(yōu)先級(jí)。當(dāng)鎖持有者釋放鎖時(shí),它的優(yōu)先級(jí)則被 “降”
回基本優(yōu)先級(jí)。在剛剛描述的場(chǎng)景中,發(fā)生鎖爭(zhēng)用時(shí)低優(yōu)先級(jí)的線(xiàn)程以高優(yōu)先級(jí)運(yùn)行,直至線(xiàn)程釋放鎖。鎖釋放后,高優(yōu)先級(jí)線(xiàn)程鎖定對(duì)象并繼續(xù)執(zhí)行。中等優(yōu)先級(jí)
線(xiàn)程禁止延遲高優(yōu)先級(jí)線(xiàn)程。圖 1 中的第二條時(shí)間線(xiàn)顯示了發(fā)生優(yōu)先級(jí)繼承時(shí)第一條時(shí)間線(xiàn)的鎖定行為的變化情況。
圖 1. 優(yōu)先級(jí)反轉(zhuǎn)和優(yōu)先級(jí)繼承
可能存在下面一種情況:高優(yōu)先級(jí)線(xiàn)程試圖獲取低優(yōu)先級(jí)線(xiàn)程持有的鎖,而低優(yōu)先級(jí)線(xiàn)程自身又被另一個(gè)線(xiàn)程持有的另一個(gè)鎖阻塞。在這種情況下,低優(yōu)先級(jí)線(xiàn)程和另一個(gè)線(xiàn)程都會(huì)被提高優(yōu)先級(jí)。就是說(shuō),優(yōu)先級(jí)繼承需要對(duì)一組線(xiàn)程進(jìn)行優(yōu)先級(jí)提高和降低。
優(yōu)先級(jí)繼承實(shí)現(xiàn)
優(yōu)先級(jí)繼承是通過(guò) Linux 內(nèi)核功能來(lái)提供的,通過(guò) POSIX 鎖定服務(wù)可將后者導(dǎo)出到用戶(hù)空間。完全位于用戶(hù)空間中的解決方案并不令人滿(mǎn)意,因?yàn)椋?/p>
- Linux 內(nèi)核可能被搶占并且常常出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)。對(duì)于某些系統(tǒng)鎖也需要使用優(yōu)先級(jí)繼承。
- 嘗試用戶(hù)空間中的解決方案導(dǎo)致難于解決的競(jìng)態(tài)條件。
- 優(yōu)先級(jí)提高總是需要使用內(nèi)核調(diào)用。
POSIX 鎖的類(lèi)型為 pthread_mutex。用于創(chuàng)建 pthread_mutex 的 POSIX API 使用互斥鎖來(lái)實(shí)現(xiàn)優(yōu)先級(jí)繼承協(xié)議。有一些 POSIX 服務(wù)可用于鎖定 pthread_mutex 和為 pthread_mutex 解鎖。在這些情況下優(yōu)先級(jí)繼承支持生效。Linux 在沒(méi)有鎖爭(zhēng)用的情況下執(zhí)行用戶(hù)空間中的所有鎖定。當(dāng)發(fā)生鎖爭(zhēng)用時(shí),在內(nèi)核空間中進(jìn)行優(yōu)先級(jí)提高和同步隊(duì)列管理。
WebSphere VM 使用 POSIX 鎖定 API 來(lái)實(shí)現(xiàn)我們先前所描述的用于支持優(yōu)先級(jí)繼承的核心 Java 語(yǔ)言同步原語(yǔ)。用戶(hù)級(jí)別的 C 代碼也可以使用這些 POSIX 服務(wù)。對(duì)于 Java 級(jí)別的鎖定操作,分配了一個(gè)惟一的 pthread_mutex 并使用原子機(jī)器操作將其綁定到 Java 對(duì)象。對(duì)于 Java 級(jí)別的解鎖操作,使用原子操作解除 pthread_mutex 與對(duì)象之間的綁定,前提是不存在鎖爭(zhēng)用。存在鎖爭(zhēng)用時(shí),POSIX 鎖定和解鎖操作將觸發(fā) Linux 內(nèi)核優(yōu)先級(jí)繼承支持。
為了幫助實(shí)現(xiàn)互斥鎖分配和鎖定時(shí)間的最小化,JVM 管理一個(gè)全局鎖緩存和一個(gè)單線(xiàn)程鎖緩存,其中每個(gè)緩存包含了未分配的 pthread_mutex。線(xiàn)程專(zhuān)用緩存中的互斥鎖從全局鎖緩存中獲得?;コ怄i在放入線(xiàn)程鎖定緩存之前被線(xiàn)程預(yù)先鎖定。非爭(zhēng)用的解鎖操作將一個(gè)鎖定的互斥鎖返回給線(xiàn)程鎖定緩存。此處假定以非爭(zhēng)用的鎖定為標(biāo)準(zhǔn),而 POSIX 級(jí)別的鎖定則通過(guò)重用預(yù)先鎖定的互斥鎖來(lái)得到減少和攤銷(xiāo)。
JVM 自身?yè)碛袃?nèi)部鎖,用于序列化對(duì)關(guān)鍵 JVM 資源(如線(xiàn)程列表和全局鎖緩存)的訪問(wèn)。這些鎖基于優(yōu)先級(jí)繼承并且其持有時(shí)間較短。
RT 應(yīng)用程序的同步考慮
本節(jié)將介紹 RT 同步的一些特性,這些特性可以幫助移植應(yīng)用程序的開(kāi)發(fā)人員使用 RT 線(xiàn)程或編寫(xiě)新的應(yīng)用程序以使用 RT 線(xiàn)程化。
普通 Java 線(xiàn)程和 RT 線(xiàn)程之間的鎖爭(zhēng)用
RT 線(xiàn)程可能被普通 Java 線(xiàn)程持有的鎖阻塞。發(fā)生這種情況時(shí),優(yōu)先級(jí)繼承接管線(xiàn)程,因此普通 Java 線(xiàn)程的優(yōu)先級(jí)被提高到 RT 線(xiàn)程的優(yōu)先級(jí),并且只要它持有鎖就一直保持該優(yōu)先級(jí)。普通 Java 線(xiàn)程繼承了 RT 線(xiàn)程的所有調(diào)度特征:
- 普通 Java 線(xiàn)程按
SCHED_FIFO 策略運(yùn)行,因此線(xiàn)程不劃分時(shí)間片。
- 在提高了優(yōu)先級(jí)的 RT 運(yùn)行隊(duì)列中進(jìn)行調(diào)度和讓步。
此行為在普通 Java 線(xiàn)程釋放鎖時(shí)返回到 SCHED_OTHER。如果 清單 1 中創(chuàng)建的兩個(gè)線(xiàn)程在持有 RT 線(xiàn)程所需要的鎖的時(shí)候都不運(yùn)行,則該程序?qū)⒉粫?huì)結(jié)束并且出現(xiàn)我們?cè)?使用 RT 線(xiàn)程的問(wèn)題代碼示例 部分中描述的問(wèn)題。因?yàn)榭赡艹霈F(xiàn)這種情形,所以對(duì)于所有在實(shí)時(shí) JVM 中執(zhí)行的線(xiàn)程來(lái)說(shuō),執(zhí)行 spin 循環(huán)和讓步并不明智。
NHRT 和 RT 線(xiàn)程之間的鎖爭(zhēng)用
NHRT
可能在 RT 線(xiàn)程(或相應(yīng)的普通 Java 線(xiàn)程)持有的鎖處阻塞。雖然 RT 線(xiàn)程持有鎖,但是 GC 可能搶占 RT 并間接地?fù)屨?
NHRT。NHRT 需要一直等到 RT 不再被 GC 搶占并釋放鎖后才能執(zhí)行。如果 NHRT 執(zhí)行的功能具有嚴(yán)格的時(shí)間要求,則 GC 搶占
NHRT 將是一個(gè)嚴(yán)重的問(wèn)題。
WebSphere Real Time
中具有確定性的垃圾收集器將暫停時(shí)間保持在一毫秒以下,使 NHRT 搶占更具有確定性。如果不能容忍此類(lèi)暫停,則可以通過(guò)避免 NHRT 和 RT
線(xiàn)程之間的鎖共享來(lái)繞過(guò)該問(wèn)題。如果強(qiáng)制使用鎖定,則可以考慮使用特定于 RT 或 NHRT 的資源和鎖。例如,實(shí)現(xiàn)線(xiàn)程入池的應(yīng)用程序可以考慮對(duì)
NHRT 和 RT 線(xiàn)程使用分開(kāi)的池和池鎖。
同樣,javax.realtime 包提供了以下的類(lèi):
-
WaitFreeReadQueue 類(lèi)主要用于將對(duì)象從 RT 線(xiàn)程傳遞到 NHRT。
-
WaitFreeWriteQueue 類(lèi)主要用于將對(duì)象從 NHRT 傳遞到 RT 線(xiàn)程。
RT 線(xiàn)程在執(zhí)行無(wú)等待操作時(shí)可能被 GC 阻塞,上述類(lèi)保證了 RT 線(xiàn)程在執(zhí)行無(wú)等待操作時(shí)不會(huì)持有 NHRT 所需的鎖。
javax.realtime 包中的同步
某些 javax.realtime 方法故意沒(méi)有實(shí)現(xiàn)同步,因?yàn)榧词规i是無(wú)爭(zhēng)用的,同步也會(huì)造成系統(tǒng)開(kāi)銷(xiāo)。如果需要同步,則調(diào)用方負(fù)責(zé)封裝同步方法或塊中所需的 javax.realtime 方法。編程人員在使用 java.realtime 包的方法時(shí)必須考慮添加此類(lèi)同步。
核心 JLS 包中的同步
相反,如 java.util.Vector 之類(lèi)的核心 JLS 服務(wù)已經(jīng)實(shí)現(xiàn)同步。同樣,某些核心 JLS 服務(wù)可以執(zhí)行一些內(nèi)部鎖定來(lái)序列化某些共享資源。由于這種同步,在使用核心 JLS 服務(wù)時(shí),必須謹(jǐn)慎執(zhí)行以避免 GC 搶占 NHRT 的問(wèn)題(參見(jiàn) NHRT 和 RT 線(xiàn)程之間的鎖爭(zhēng)用)。
無(wú)爭(zhēng)用鎖定的性能
非 RT 應(yīng)用程序的標(biāo)準(zhǔn)檢查和檢測(cè)已表明鎖定主要是無(wú)爭(zhēng)用的。無(wú)爭(zhēng)用鎖定同樣被認(rèn)為是 RT 應(yīng)用程序中的主要類(lèi)型,特別是現(xiàn)有組件或庫(kù)需要重用的時(shí)候。如果已知鎖定是無(wú)爭(zhēng)用的但是難以避免或刪除同步指令,則對(duì)這些鎖定花費(fèi)一點(diǎn)小的確定的開(kāi)銷(xiāo)不失為明智的做法。
如前所述,無(wú)爭(zhēng)用鎖定操作涉及了一些設(shè)置和一個(gè)原子機(jī)器指令。解鎖操作涉及一個(gè)原子機(jī)器指令。鎖定操作的設(shè)置涉及分配一個(gè)預(yù)先鎖定的互斥鎖。該分配被認(rèn)為是無(wú)爭(zhēng)用鎖定操作中最大的可變開(kāi)銷(xiāo)。RealtimeSystem.setMaximumConcurrentLocks() 可以幫助控制這種可變開(kāi)銷(xiāo)。
RealtimeSystem.setMaximumConcurrentLocks(int numLocks) 使 WebSphere Real Time 中的 VM 將 numLocks 互斥鎖預(yù)先分配給全局鎖緩存。全局鎖緩存將其內(nèi)容提供給單線(xiàn)程鎖緩存。通過(guò)使用這個(gè) RealTimeSystem API,可以降低具有嚴(yán)格時(shí)間要求的代碼區(qū)域中發(fā)生鎖定初始化的機(jī)率。RealTimeSystem.getMaximumConcurrentLocks() 可以用來(lái)幫助決定 setMaximumConcurentLocks() 調(diào)用中應(yīng)該使用的數(shù)量,但是要注意 getMaximumConcurrentLocks() 提供關(guān)于調(diào)用的鎖使用,而不是最高使用標(biāo)記(high-water mark)。未來(lái)的 RTSJ 版本可能提供 API 以便提供最高使用標(biāo)記。不要為 numLocks 的值提供很大的值,因?yàn)檎{(diào)用 setMaximimConcurrentLocks() 可能耗費(fèi)過(guò)量的時(shí)間和內(nèi)存來(lái)創(chuàng)建那些鎖。還要注意:這個(gè) API 是定義為 JVM 專(zhuān)用的,因此其他 JVM 可能忽略該調(diào)用或提供不同的行為。
爭(zhēng)用鎖定的性能
一個(gè)線(xiàn)程可以同時(shí)持有多個(gè)鎖,并且可能已經(jīng)按某種順序獲得了這些鎖。所有此類(lèi)鎖定模式形成了一個(gè)鎖層次結(jié)構(gòu)。優(yōu)先級(jí)繼承可以指提高和降低一組線(xiàn)程的優(yōu)先級(jí)。組中線(xiàn)程的數(shù)量應(yīng)該不大于系統(tǒng)中最深的鎖層次結(jié)構(gòu)的深度。通過(guò)保持較淺的鎖層次結(jié)構(gòu),可以鎖定最少量的對(duì)象,您能夠影響最大量的需要調(diào)整優(yōu)先級(jí)的線(xiàn)程。
同步操作中的時(shí)間
Object.wait(long timeout, int nanos) 為相關(guān)的等待操作提供納秒粒度。HighResolutionTime.waitForObject() API 與 Object.wait() 類(lèi)似并提供可使用納秒粒度指定的相對(duì)時(shí)間和絕對(duì)時(shí)間。在 WebSphere Real Time 中,這兩個(gè) API 都使用底層 POSIX 鎖定等待服務(wù)來(lái)實(shí)現(xiàn)。這些底層服務(wù)最多提供微秒粒度。如有需要,出于可移植性目的,javax.realtime 包的 Clock 類(lèi)的 getResolution() 方法應(yīng)用于檢索執(zhí)行平臺(tái)的分辨率。
結(jié)束語(yǔ)
RTSJ 通過(guò) javax.realtime
包中的新 RT 類(lèi)和 API 擴(kuò)展并加強(qiáng)了 Java 編程人員的線(xiàn)程化和同步功能。在 WebSphere Real Time 中,這些功能通過(guò)
Linux 內(nèi)核的 RT 版本來(lái)實(shí)現(xiàn),對(duì) POSIX 線(xiàn)程化進(jìn)行修改并對(duì) JVM 自身進(jìn)行修改。您現(xiàn)在對(duì) RTSJ
線(xiàn)程化和同步有了更深入的了解,這些知識(shí)可以幫助您在編寫(xiě)和部署 RT 應(yīng)用程序時(shí)避免問(wèn)題的發(fā)生。
參考資料
學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
-
WebSphere Real Time:WebSphere Real Time 利用了標(biāo)準(zhǔn)的 Java 技術(shù)并且沒(méi)有損失確定性,使應(yīng)用程序依賴(lài)于精確的響應(yīng)時(shí)間。
-
Real-time Java 技術(shù):訪問(wèn)作者的 IBM alphaWorks 研究站點(diǎn),查找用于實(shí)時(shí) Java 的先進(jìn)技術(shù)。
討論
作者簡(jiǎn)介
 |
|
|
 |
Patrick
Gallop 于 1984 年畢業(yè)于 Waterloo 大學(xué)并獲得數(shù)學(xué)碩士學(xué)位。之后不久就加入了 IBM
多倫多實(shí)驗(yàn)室并從事各種編譯器和編譯器工具項(xiàng)目,包括用于不同操作系統(tǒng)和架構(gòu)的 C 和靜態(tài) Java 編譯器。Patrick 致力于多個(gè)版本的
IBM Java 項(xiàng)目開(kāi)發(fā),最近成為 IBM Real-time Java 項(xiàng)目的高級(jí)開(kāi)發(fā)人員。
|
 |
|

|
 |
Mark
Stoodley 在 2001 年從多倫多大學(xué)獲得計(jì)算機(jī)工程的博士學(xué)位,并于 2002 年加入 IBM 多倫多實(shí)驗(yàn)室,研究 Java JIT
編譯技術(shù)。從 2005 年起,通過(guò)采用現(xiàn)有的 JIT 編譯器并在實(shí)時(shí)環(huán)境中進(jìn)行操作,他開(kāi)始為 IBM WebSphere Real Time
開(kāi)發(fā) JIT 技術(shù)。他現(xiàn)在是 Java
編譯控制團(tuán)隊(duì)的團(tuán)隊(duì)負(fù)責(zé)人,從事在本地代碼的執(zhí)行環(huán)境中提高本地代碼編譯效率的工作。工作以外,他喜歡收拾自己的家。
|
|