免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
實時 Java,第 3 部分: 線程化和同步
本文是關于實時 Java™系列文章(共 5 部分)的第三篇,考察了 Java 實時規(guī)范(RTSJ)的實現(xiàn)必須支持的線程化和同步問題。您還將了解開發(fā)和部署實時應用程序時必須牢記的一些有關這兩方面的基本考慮。

線程化和同步是 Java 編程語言的核心特性,Java 語言規(guī)范(JLS)中對二者作出了描述。RTSJ 用多種方式擴展了 JLS 的核心功能。(參見 參考資料 中關于 JLS 和 RTSJ 的鏈接。)例如,RTSJ 引入了一些新的實時(RT)線程類型,它們必須遵守比普通 Java 線程更加嚴格的調(diào)度策略。另一個例子是優(yōu)先級繼承,它是一種鎖定策略,定義了鎖競爭時如何管理鎖同步。

理解對優(yōu)先級和優(yōu)先級序列的管理有助于理解 RTSJ 針對線程化和同步所作的更改。優(yōu)先級也是 RT應用程序使用的一種重要工具。本文通過討論如何管理線程優(yōu)先級和優(yōu)先級序列來描述 RTSJ 線程化和同步。討論了開發(fā)、部署和執(zhí)行 RT應用程序(包括使用 IBM WebSphere® Real Time 開發(fā)的應用程序,參見 參考資料)時應該考慮的一些方面。

理解普通的 Java 線程

JLS 中定義的線程稱為普通 Java 線程。普通 Java 線程是 java.lang.Thread 類的一個實例,該類擁有從 1 到 10 的 10 個優(yōu)先級別。為了適應大量的執(zhí)行平臺,JLS 在如何實現(xiàn)、調(diào)度和管理普通 Java 線程的優(yōu)先級方面提供了很大的靈活性。

WebSphere VMs on Linux®(包括 WebSphere Real Time)使用 Linux 操作系統(tǒng)提供的本地線程化服務。您可以通過理解 Linux 的線程化和同步來學習 Java 的線程化和同步。

Linux 線程化和同步

Linux 操作系統(tǒng)發(fā)展至今已經(jīng)提供了不同用戶級別的線程化實現(xiàn)。Native POSIX Thread Library(NPTL)(參見 參考資料)是 Linux 最新版本的戰(zhàn)略性線程化實現(xiàn),由 WebSphere VMs 所使用。NPTL 與它的前任相比優(yōu)勢在于 POSIX 兼容性和性能。在編譯時可通過系統(tǒng)的頭文件獲取 POSIX 服務。可在運行時通過 libpthread.so 動態(tài)庫和底層 Linux 核心支持獲取 POSIX 服務。Linux 核心可以根據(jù)靜態(tài)控制(如線程優(yōu)先級級別)和系統(tǒng)中執(zhí)行的線程的某些動態(tài)條件下來執(zhí)行線程調(diào)度。

POSIX 允許您創(chuàng)建具有不同線程調(diào)度策略和優(yōu)先級的 POSIX 線程(pthreads)以滿足不同應用程序的需求。下面是三種此類的調(diào)度策略:

  • SCHED_OTHER
  • SCHED_FIFO
  • SCHED_RR

SCHED_OTHER 策略用于傳統(tǒng)用戶任務,如程序開發(fā)工具、辦公應用程序和 Web 瀏覽器。 SCHED_RRSCHED_FIFO 主要用于具有更高的確定性和時限需求的應用程序。SCHED_RRSCHED_FIFO 之間的主要區(qū)別是 SCHED_RR 分時間片 執(zhí)行線程,而 SCHED_FIFO 則不是這樣。SCHED_OTHERSCHED_FIFO 策略用于 WebSphere Real Time,并在下面作出了更加詳細的描述。(我們不介紹 SCHED_RR 策略,WebSphere Real Time 沒有使用它。)

POSIX 通過 pthread_mutex 數(shù)據(jù)類型提供鎖定和同步支持。pthread_mutex 可以使用不同的鎖定策略創(chuàng)建。當多個線程需要同時獲取同一個鎖的時候,鎖定策略常常會影響執(zhí)行行為。標準的 Linux 版本支持單個的默認策略,而 RT Linux 版本還支持優(yōu)先級繼承鎖定策略。我們將在本文的 同步概述 一節(jié)對優(yōu)先級繼承策略作更詳細的描述。

Linux 調(diào)度和鎖定用來管理先進先出(FIFO)隊列。

普通 Java 線程的線程調(diào)度

RTSJ 指出普通 Java 線程的行為跟 JLS 中定義的相同。在 WebSphere Real Time 中,普通 Java 線程使用 Linux 的 POSIX SCHED_OTHER 調(diào)度策略來實現(xiàn)。SCHED_OTHER 策略主要用于編譯器和字處理程序之類的應用程序,不能用于需要更高確定性的任務。

在 2.6 Linux 內(nèi)核中,SCHED_OTHER 策略支持 40 個優(yōu)先級級別。這 40 個優(yōu)先級級別基于處理器級別來管理,就是說:

  • 出于緩存性能的原因,Linux 嘗試在同一個處理程序中執(zhí)行線程。
  • 線程調(diào)度主要使用處理器級別的鎖而不是系統(tǒng)級別的鎖。

如有需要,Linux 可將線程從一個處理程序遷移到另一個處理程序以平衡工作量。

在(40 個中的)每個優(yōu)先級級別中,Linux 管理活動隊列過期隊列。每個隊列包含一個線程鏈表(或者為空)。使用活動和過期隊列出于以下目的:效率、負載平衡和其他一些目的。邏輯上可將系統(tǒng)看作:為(40 個中的)每個優(yōu)先級管理一個 FIFO 序列,稱為運行隊列。一個從非空運行隊列的前端分派的線程具有最高的優(yōu)先級。該線程從隊列中移除并執(zhí)行一段時間(稱作:時間量時間片)。當一個執(zhí)行線程超過 它的時間量時,它的優(yōu)先級被放在運行隊列的后端并給它指定了新的時間量。通過從隊列的前端分派線程和在隊列的后端放置過期的線程,程序在一個優(yōu)先級中輪替執(zhí)行。

為線程提供的時間量取決于給線程指定的優(yōu)先級。指定了較高優(yōu)先級的線程擁有較長的執(zhí)行時間量。為了防止線程霸占 CPU,Linux 根據(jù)一些因素(如線程是 I/O 限制還是 CPU 限制)動態(tài)提高或降低線程的優(yōu)先級。線程可以通過讓步(如調(diào)用 Thread.yield())自愿地放棄它的時間片,或通過阻塞放棄控制權,在阻塞處等待事件發(fā)生。釋放鎖可以觸發(fā)一個這類的事件。

WebSphere Real Time 中的 VM 沒有顯式地指定跨越 40 個 SCHED_OTHER Linux 線程優(yōu)先級的 10 個普通 Java 線程優(yōu)先級。所有的普通 Java 線程,不論其 Java 優(yōu)先級如何,都被指定為默認的 Linux 優(yōu)先級。默認的 Linux 優(yōu)先級處于 40 個 SCHED_OTHER優(yōu)先級的中間位置。通過使用默認優(yōu)先級,普通 Java 線程可以順利地執(zhí)行,即不論 Linux 可能作出何種動態(tài)優(yōu)先級調(diào)整,運行隊列中的每個普通Java 線程都能最終得到執(zhí)行。這里假設的是只執(zhí)行普通 Java 線程的系統(tǒng)而不是其他系統(tǒng),比如執(zhí)行 RT 線程的系統(tǒng)。

注意:WebSphere Real Time 中的 VM 和 WebSphere VM 的非 RT 版本都使用 SCHED_OTHER策略并針對普通 Java 線程使用默認優(yōu)先級指定。通過使用相同的策略,這兩種 JVM 具有相似但不相同的線程調(diào)度和同步特征。WebSphereReal Time 類庫中的更改、JVM 中的更改和為支持 RTSJ 而在 JIT 編譯器中作出的更改,以及 RT Metronome垃圾收集器的引入(參見 參考資料)使應用程序不可能在兩種虛擬機中以相同的同步和性能特征運行。在 IBM WebSphere Real Time 測試期間,在測試程序中,同步差異使競爭條件(換言之,bug)浮出了水面,而這些測試程序已經(jīng)在其他 JVM 上運行了很多年。

關于本文的代碼示例的一點注意

以下各節(jié)中的代碼示例使用帶有讓步的 spin 循環(huán)。這種方法只適用于演示的目的;不能在 RT 應用程序中使用這種方法。

使用普通 Java 線程的代碼示例

清單 1 展示了一個使用普通 Java 線程的程序,確定了兩個線程中的每一個在五秒的時間間隔內(nèi)在一個循環(huán)中執(zhí)行的迭代次數(shù):


清單 1. 普通 Java 線程
            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 中的程序具有三個普通 Java 線程的用戶線程:

  • 原始線程:
    • 它是啟動過程中隱式創(chuàng)建的主線程,執(zhí)行 main() 方法。
    • main() 創(chuàng)建了兩個普通 Java 線程:一個線程的優(yōu)先級為 4 而另一個線程的優(yōu)先級為 6。
    • 主線程通過調(diào)用 Thread.sleep() 方法休眠五秒鐘來達到故意阻塞自身的目的。
    • 休眠五秒鐘后,此線程指示其他兩個線程結束。
  • 優(yōu)先級為 4 的線程:
    • 此線程由原始線程創(chuàng)建,后者執(zhí)行包含 for 循環(huán)的 run() 方法。
    • 該線程:
      1. 在每次循環(huán)迭代中增加一個計數(shù)。
      2. 通過調(diào)用 Thread.yield() 方法自愿放棄它的時間片。
      3. 在主線程發(fā)出請求時結束。結束前打印循環(huán)計數(shù)。
  • 優(yōu)先級為 6 的線程:此線程執(zhí)行的動作與優(yōu)先級為 4 的線程相同。

如果此程序在單處理器或卸載的多處理器上運行,則每個線程打印的 for 循環(huán)迭代計數(shù)大致相同。在一次運行中,程序?qū)⒋蛴。?/p>

Created thread            Created thread            Thread terminates. Loop count is 540084            Thread terminates. Loop count is 540083            

如果刪除對 Thread.yield() 的調(diào)用,則兩個線程的循環(huán)計數(shù)可能相近,但絕不可能相同。在 SCHED_OTHER策略中為這兩個線程都指定了相同的默認優(yōu)先級。因此給兩個線程分配了相同的時間片執(zhí)行。因為線程執(zhí)行的是相同的代碼,所以它們應作出類似的動態(tài)優(yōu)先級調(diào)整并在相同的運行隊列中輪替執(zhí)行。但是由于首先執(zhí)行優(yōu)先級為 4的線程,因此在五秒鐘的執(zhí)行時間間隔中,它分得的時間稍多一些并且打印的循環(huán)計數(shù)也稍大一些。





回頁首


理解 RT 線程

RT 線程是 javax.realtime.RealtimeThread 的一個實例。RTSJ 要求規(guī)范的實現(xiàn)必須為 RT 線程提供至少 28 個連續(xù)的優(yōu)先級。這些優(yōu)先級被稱作實時優(yōu)先級。規(guī)范中并沒有指定 RT 優(yōu)先級范圍的開始值,除非其優(yōu)先級高于 10 —— 普通 Java 線程的最高優(yōu)先級值。出于可移植性的原因,應用程序代碼應使用新的 PriorityScheduler 類的 getPriorityMin()getPriorityMax() 方法來確定可用的 RT 優(yōu)先級值的范圍。

對 RT 線程的推動

JLS 中的線程調(diào)度并不精確而且只提供了 10 個優(yōu)先級值。由 Linux 實現(xiàn)的 POSIX SCHED_OTHER 策略滿足了各種應用程序的需要。但是 SCHED_OTHER 策略具有一些不好的特性。動態(tài)優(yōu)先級調(diào)整和時間片劃分可能在不可預測的時間內(nèi)發(fā)生。SCHED_OTHER 優(yōu)先級的值(40)其實并不算大,其中一部分已經(jīng)被使用普通 Java 線程的應用程序和動態(tài)優(yōu)先級調(diào)整利用了。JVM 還需要對內(nèi)部線程使用優(yōu)先級以達到一些特殊目的,比如垃圾收集(GC)。

缺少確定性、需要更高的優(yōu)先級級別以及要求與現(xiàn)有應用程序兼容,這些因素引發(fā)了對擴展的需求,這將為 Java 程序員提供新的調(diào)度功能。RTSJ 中描述的 javax.realtime 包中的類提供了這些功能。在 WebSphere Real Time 中,Linux SCHED_FIFO 調(diào)度策略滿足了 RTSJ 調(diào)度需求。

RT Java 線程的線程調(diào)度

在 WebSphere Real Time 中,支持 28 個 RT Java 優(yōu)先級,其范圍為 11 到 38。PriorityScheduler 類的 API 應用于檢索這個范圍。本節(jié)描述了比 RTSJ 更多的線程調(diào)度細節(jié)以及 Linux SCHED_FIFO 策略的一些方面,已經(jīng)超出了 RTSJ 的需求。

RTSJ 將 RT 優(yōu)先級視作由運行時系統(tǒng)在邏輯上實現(xiàn)的優(yōu)先級,該系統(tǒng)為每個 RT優(yōu)先級保持一個獨立隊列。線程調(diào)度程序必須從非空的最高優(yōu)先級隊列的頭部開始調(diào)度。注意:如果所有隊列中的線程都不具有 RT 優(yōu)先級,則調(diào)度一個普通Java 線程按 JLS 中的描述執(zhí)行(參見 普通 Java 線程的線程調(diào)度)。

具有 RT 優(yōu)先級的調(diào)度線程可以一直執(zhí)行直至阻塞,通過讓步自愿放棄控制權,或被具有更高 RT 優(yōu)先級的線程搶占。具有 RT優(yōu)先級并自愿讓步的線程的優(yōu)先級被置于隊列的后端。RTSJ 還要求此類調(diào)度在不變的時間內(nèi)進行,并且不能隨某些因素變化(如當前執(zhí)行的 RT線程的數(shù)量)。RTSJ 的 1.02 版本對單處理器系統(tǒng)應用了這些規(guī)則;RTSJ 對于多處理器系統(tǒng)上的調(diào)度如何運作未作要求。

Linux 為所有適當?shù)?RTSJ 調(diào)度需求提供了 SCHED_FIFO 策略。SCHED_FIFO 策略用于 RT 而不用于用戶任務。SCHED_FIFOSCHED_OTHER 策略的區(qū)別在于前者提供了 99 個優(yōu)先級級別。SCHED_FIFO 不為線程分時間片。同樣,SCHED_FIFO 策略也不動態(tài)調(diào)整 RT 線程的優(yōu)先級,除非通過優(yōu)先級繼承鎖定策略(同步概述 一節(jié)對此作出了描述)。由于優(yōu)先級繼承的原因,RTSJ 需要使用優(yōu)先級調(diào)整。

Linux 為 RT 線程和普通 Java 線程提供不變時間調(diào)度。在多處理器系統(tǒng)中,Linux 試圖模擬分派到可用處理器的單個全局 RT 線程隊列的行為。這與 RTSJ 的精神最為接近,但確實與用于普通 Java 線程的 SCHED_OTHER 策略不同。

使用 RT 線程的有問題的代碼示例

清單 2 修改 清單 1 中的代碼來創(chuàng)建 RT 線程而不是普通 Java 線程。使用 java.realtime.RealtimeThread 而不是 java.lang.Thread 指出了其中的區(qū)別。第一個線程創(chuàng)建于第 4 RT 優(yōu)先級而第二個線程創(chuàng)建于第 6 RT 優(yōu)先級,與 getPriorityMin() 方法確定的相同。


清單 2. RT 線程
            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 中修改后的代碼存在一些問題。如果程序在單處理器環(huán)境中運行,則它永遠不會結束并且只能打印以下內(nèi)容:

Created thread            

出現(xiàn)這樣的結果可以用 RT 線程調(diào)度的行為來解釋。原始線程仍然是一個普通 Java 線程并利用非 RT(SCHED_OTHER)策略運行。只要原始線程啟動第一個 RT 線程,RT 線程就搶占原始線程并且 RT 線程將會不確定地運行,因為它不受時間量和線程阻塞的限制。原始線程被搶占后,就再也不允許執(zhí)行,因此再也不會啟動第二個 RT 線程。Thread.yield() 對允許原始線程執(zhí)行反而不起作用 —— 因為讓步邏輯將 RT 線程置于其運行隊列的末端 —— 但是線程調(diào)度程序?qū)⒃俅握{(diào)度這個線程,因為它是運行隊列前端的具有最高優(yōu)先級的線程。

該程序在雙處理器系統(tǒng)中同樣會失敗。它將打印以下內(nèi)容:

Created thread            Created thread            

允許使用原始線程創(chuàng)建這兩個 RT 線程。但是創(chuàng)建第二個線程后,原始線程被搶占并且再也不允許告知線程結束,因為兩個 RT 線程在兩個處理器上執(zhí)行而且永遠不會阻塞。

在帶有三個或更多處理器的系統(tǒng)上,程序運行至完成并生成一個結果。

單處理器上運行的 RT 代碼示例

清單 3 顯示了修改后能在單處理器系統(tǒng)中正確運行的代碼。main()方法的邏輯被移到了一個具有第 8 RT 優(yōu)先級的 “main” RT 線程中。這個優(yōu)先級比主 RT 線程創(chuàng)建的兩個其他 RT線程的優(yōu)先級都要高。擁有最高的 RT 優(yōu)先級使這個主 RT 線程能夠成功地創(chuàng)建兩個 RT線程,并且還允許它從五秒鐘的休眠中蘇醒時能夠搶占當前運行的線程。


清單 3. 修改后的 RT 線程示例
            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);            }            }            

當此程序在單處理器上運行時,它將打印以下結果:

Created thread            Thread terminates. Loop count is 32767955            Created thread            Thread terminates. Loop count is 0            

程序的輸出顯示所有的線程運行并結束,但是這兩個線程只有一個執(zhí)行 for 循環(huán)的一個迭代。這個輸出可通過考慮 RT 線程的優(yōu)先級來解釋。主 RT 線程一直運行,直至調(diào)用 Thread.sleep()方法來阻塞線程。主 RT 線程創(chuàng)建了兩個 RT 線程,但是只有第二個 RT 線程(具有第 6 RT 優(yōu)先級)才能夠在主 RT線程休眠時運行。這個線程一直運行,直至主 RT 線程從休眠中蘇醒并指示線程結束。主 RT 線程一旦結束,就允許執(zhí)行具有第 6優(yōu)先級的線程并結束。程序按這種方式執(zhí)行并打印具有非零值循環(huán)計數(shù)。此線程結束后,就允許運行具有第 4 RT 優(yōu)先級的線程,但它只是繞過 for 循環(huán),因為系統(tǒng)指示結束該線程。該線程將打印零循環(huán)計數(shù)值然后結束。





回頁首


RT 應用程序的線程化考慮

移植應用程序以使用 RT 線程或編寫新應用程序以利用 RT 線程化時需要考慮 RT 線程化的一些特性,本節(jié)將討論這些特性。

RT 線程的新擴展

RTSJ 指定了一些工具,用于創(chuàng)建在某個特定或相關時間啟動的 RT 線程。您可以創(chuàng)建一個線程,用于在指定的時間間隔或時期內(nèi)運行某種邏輯。您可以定義一個線程,用于未在指定時期內(nèi)完成此邏輯時執(zhí)行(激發(fā))一個 AsynchronousEventHandler(AEH)。您還可以定義線程所能夠使用的內(nèi)存類型和數(shù)量的限制,如果超過該限制,則拋出 OutOfMemoryError。這些工具只對 RT 線程可用,而對普通 Java 線程不可用。您可以在 RTSJ 中找到關于這些工具的更多信息。

Thread.interrupt() 和 pending 異常

RT 線程擴展了 Thread.interrupt() 行為。此 API 會像 JLS 中描述的那樣中斷被阻塞的進程。如果用戶在方法聲明中加入 Throws AsynchronouslyInterruptedException 子句,顯式地將其標記為可中斷,也會引起這個異常。該異常也會困擾 線程,用戶必須顯式地清除異常;否則它會一直困擾(術語為 pending)線程。如果用戶不清除異常,則線程會伴隨著該異常而結束。如果線程以 “常規(guī)” 形式結束,但是不是在按自身形式進行 RT 線程入池的應用程序中,這種錯誤危害不大,就是說,線程返回池中時仍然隨附 InterruptedException。在這種情況下,執(zhí)行線程入池的代碼應顯式地清除異常;否則,當重新分配具有隨附異常的入池線程時,可能欺騙性地拋出異常。

原始線程和應用程序調(diào)度邏輯

原始線程通常都是普通 Java 線程 —— 而不是 RT 線程。第一個 RT 線程總是由普通 Java線程創(chuàng)建。如果沒有足夠的可用處理器來同時運行 RT 線程和普通 Java 線程,則這個 RT 線程會立即搶占普通 Java線程。搶占可以防止普通 Java 線程繼續(xù)創(chuàng)建 RT 線程或其他邏輯,以便將應用程序置于適當?shù)某跏蓟癄顟B(tài)。

您可以通過從一個高優(yōu)先級 RT線程執(zhí)行應用程序初始化來避免這個問題。執(zhí)行自身形式的線程入池和線程調(diào)度的應用程序或庫可能需要這種技術。即,線程調(diào)度邏輯應該以高優(yōu)先級運行,或在高優(yōu)先級的線程中運行。為執(zhí)行線程入池邏輯選擇適當?shù)膬?yōu)先級有助于防止線程入隊和出隊中遇到的問題。

失控線程

普通 Java 線程按時間量執(zhí)行,而動態(tài)優(yōu)先級根據(jù) CPU 的使用調(diào)整調(diào)度程序的執(zhí)行,允許所有的普通 Java 線程最后執(zhí)行。反過來,RT線程不受時間量的限制,并且線程調(diào)度程序不根據(jù) CPU 的使用進行任何形式的動態(tài)優(yōu)先級調(diào)整。普通 Java 線程和 RT線程之間的調(diào)度策略差異使失控 RT 線程的出現(xiàn)成為可能。失控 RT 線程可以控制系統(tǒng)并阻止所有其他應用程序的運行,阻止用戶登錄系統(tǒng)等等。

在開發(fā)和測試期間,有一種技術可以幫助減輕失控線程的影響,即限制進程能夠使用的 CPU 數(shù)量。在Linux 上,限制 CPU 的使用使進程在耗盡 CPU 限制時終止失控線程。另外,監(jiān)控系統(tǒng)狀態(tài)或提供系統(tǒng)登錄的程序應該以高 RT優(yōu)先級運行,以便程序可以搶占問題線程。

從 Java 優(yōu)先級到操作系統(tǒng)優(yōu)先級的映射

在 Linux 上,POSIX SCHED_FIFO策略提供了從 1 到 99 的整數(shù)范圍內(nèi)的 99 個 RT 優(yōu)先級。在這個系統(tǒng)范圍內(nèi),從 11 到 89 的優(yōu)先級由 WebSphere VM使用,此范圍的一個子集用來實現(xiàn) 28 個 RTSJ 優(yōu)先級。28 個 RT Java 優(yōu)先級映射到此范圍的 POSIX 系統(tǒng)優(yōu)先級,IBMWebSphere Real Time 文檔中對這一點作出了描述。但是應用程序代碼不應該依賴這個映射,而只應該依賴于 Java 級別的 28個 RT 優(yōu)先級的相關順序。這樣 JVM 可以在未來的 WebSphere Real Time 版本中重新映射這個范圍并提供改進。

如果某些端口監(jiān)督程序或 RT 進程需要的優(yōu)先級高于或低于 WebSphere Real Time 中使用的優(yōu)先級,則應用程序可以使用 SCHED_FIFO 優(yōu)先級 1 或優(yōu)先級 90 來實現(xiàn)這些程序或進程。

JNI AttachThread()

Java Native Interface (JNI) 允許使用 JNI AttachThread()API 將使用 C 代碼創(chuàng)建的線程加入到 JVM 中,但 RTSJ 并不對 JNI 接口進行更改或配置以便加入 RT線程。因此,應用程序應避免用 C 代碼創(chuàng)建準備加入到 JVM 中的 POSIX RT 線程。反過來,應該在 Java 語言中創(chuàng)建此類 RT線程。

派生進程和 RT 優(yōu)先級

一個線程可以派生另一個進程。在 Linux 上,派生進程的原始線程繼承派生它的父線程的優(yōu)先級。如果派生進程是一個 JVM,則 JVM的原始線程創(chuàng)建時具有 RT 優(yōu)先級。這將與普通 Java 線程的順序沖突,比如原始線程的調(diào)度優(yōu)先級比 RT 線程低。為了防止這種情形,JVM強制原始線程擁有非 RT 優(yōu)先級 —— 即擁有 SCHED_OTHER 策略。

Thread.yield()

Thread.yield() 只讓步給具有相同優(yōu)先級的線程,決不會讓步給高于或低于自身優(yōu)先級的線程。只讓步給具有相同優(yōu)先級的線程意味著在使用多個 RT 優(yōu)先級的 RT 應用程序中使用 Thread.yield() 可能會出現(xiàn)問題。應該避免使用 Thread.yield(),除非完全有必要。

NoHeapRealtimeThreads

javax.realtime.NoHeapRealtimeThread (NHRT) 是 RTSJ 中的另一種新的線程類型,它是 javax.realtime.RealtimeThread 的一個子類。NHRT 具有與我們所描述的 RT 線程相同的調(diào)度特征,只是 NHRT 不會被 GC 搶占并且 NHRT 無法讀或?qū)?Java 堆。NHRT 是 RTSJ 的一個重要方面,本系列的后續(xù)文章中將對它進行討論。

AsynchronousEventHandlers

AsynchronousEventHandler (AEH) 是 RTSJ 附帶的新增程序,可將它視為發(fā)生事件時執(zhí)行的一種 RT 線程。例如,可以設置 AEH 在某個特定或關聯(lián)時間激發(fā)。AEH 還具有與 RT 線程相同的調(diào)度特征并具有堆和非堆兩種風格。





回頁首


同步概述

許多 Java 應用程序直接使用 Java 線程化特性,或正在開發(fā)中的應用程序使用涉及多個線程的庫。多線程編程中的一個主要考慮是確保程序在執(zhí)行多線程的系統(tǒng)中正確地 —— 線程安全地 —— 運行。要保證程序線程安全地運行,需要序列化訪問由多個使用同步原語(如鎖或原子機器操作)的線程共享的數(shù)據(jù)。RT 應用程序的編程人員通常面臨使程序按某種時間約束執(zhí)行的挑戰(zhàn)。為了應對這個挑戰(zhàn),他們可能需要了解當前使用組件的實現(xiàn)細節(jié)、含意和性能屬性。

本文的剩余部分將討論 Java 語言提供的核心同步原語的各個方面,這些原語在 RTSJ 中如何更改,以及 RT 編程人員使用這些原語時需要注意的一些暗示。

Java 語言同步概述

Java 語言提供了三種核心同步原語:

  • 同步的方法和代碼塊允許線程在入口處鎖定對象并在出口處解鎖(針對方法或代碼塊)。
  • Object.wait() 釋放對象鎖,線程等待。
  • Object.notify()wait() 對象的線程解鎖。notifyAll() 為所有等待的線程解鎖。

執(zhí)行 wait()notify() 的線程當前必須已經(jīng)鎖定對象。

當線程試圖鎖定的對象已被其他線程鎖定時將發(fā)生鎖爭用。當發(fā)生這種情況時,沒有獲得鎖的線程被置于對象的鎖爭用者的一個邏輯隊列中。類似地,幾個線程可能對同一個對象執(zhí)行 Object.wait(),因此該對象擁有一個等待者的邏輯隊列。JLS 沒有指定如何管理這些隊列,但是 RTSJ 規(guī)定了這個行為。

基于優(yōu)先級的同步隊列

RTSJ的原理是所有的線程隊列都是 FIFO 并且是基于優(yōu)先級的?;趦?yōu)先級的 FIFO 行為 ——在前面的同步示例中,將接著執(zhí)行具有最高優(yōu)先級的線程 —— 也適用于鎖爭用者和鎖等待者的隊列。從邏輯觀點來看,鎖爭用者的 FIFO基于優(yōu)先級的隊列與等待執(zhí)行的線程執(zhí)行隊列相似。同樣有相似的鎖等待者隊列。

釋放鎖以后,系統(tǒng)從爭用者的最高優(yōu)先級隊列的前端選擇線程,以便試圖鎖定對象。類似地,完成 notify() 以后,等待者的最高優(yōu)先級隊列前端的線程從等待中解除阻塞。鎖釋放或鎖 notify() 操作與調(diào)度分派操作類似,因為都是對最高優(yōu)先級隊列頭部的線程起作用。

為了支持基于優(yōu)先級的同步,需要對 RT Linux 作一些修改。還需要對 WebSphere Real Time 中的 VM 作出更改,以便在執(zhí)行 notify() 操作時委托 Linux 選擇對哪一個線程解除阻塞。

優(yōu)先級反轉和優(yōu)先級繼承

優(yōu)先級反轉 指的是阻塞高優(yōu)先級線程的鎖由低優(yōu)先級線程持有。中等優(yōu)先級線程可能搶占低優(yōu)先級線程,同時持有鎖并優(yōu)先于低優(yōu)先級線程運行。優(yōu)先級反轉將延遲低優(yōu)先級線程和高優(yōu)先級線程的執(zhí)行。優(yōu)先級反轉導致的延遲可能導致無法滿足關鍵的時限。圖 1 的第一條時間線顯示這種情況。

優(yōu)先級繼承是一種用于避免優(yōu)先級反轉的技術。優(yōu)先級繼承由 RTSJ規(guī)定。優(yōu)先級繼承背后的思想是鎖爭用,鎖持有者的優(yōu)先級被提高到希望獲取鎖的線程的優(yōu)先級。當鎖持有者釋放鎖時,它的優(yōu)先級則被 “降”回基本優(yōu)先級。在剛剛描述的場景中,發(fā)生鎖爭用時低優(yōu)先級的線程以高優(yōu)先級運行,直至線程釋放鎖。鎖釋放后,高優(yōu)先級線程鎖定對象并繼續(xù)執(zhí)行。中等優(yōu)先級線程禁止延遲高優(yōu)先級線程。圖 1 中的第二條時間線顯示了發(fā)生優(yōu)先級繼承時第一條時間線的鎖定行為的變化情況。


圖 1. 優(yōu)先級反轉和優(yōu)先級繼承

可能存在下面一種情況:高優(yōu)先級線程試圖獲取低優(yōu)先級線程持有的鎖,而低優(yōu)先級線程自身又被另一個線程持有的另一個鎖阻塞。在這種情況下,低優(yōu)先級線程和另一個線程都會被提高優(yōu)先級。就是說,優(yōu)先級繼承需要對一組線程進行優(yōu)先級提高和降低。

優(yōu)先級繼承實現(xiàn)

優(yōu)先級繼承是通過 Linux 內(nèi)核功能來提供的,通過 POSIX 鎖定服務可將后者導出到用戶空間。完全位于用戶空間中的解決方案并不令人滿意,因為:

  • Linux 內(nèi)核可能被搶占并且常常出現(xiàn)優(yōu)先級反轉。對于某些系統(tǒng)鎖也需要使用優(yōu)先級繼承。
  • 嘗試用戶空間中的解決方案導致難于解決的競態(tài)條件。
  • 優(yōu)先級提高總是需要使用內(nèi)核調(diào)用。

POSIX 鎖的類型為 pthread_mutex。用于創(chuàng)建 pthread_mutex 的 POSIX API 使用互斥鎖來實現(xiàn)優(yōu)先級繼承協(xié)議。有一些 POSIX 服務可用于鎖定 pthread_mutex 和為 pthread_mutex 解鎖。在這些情況下優(yōu)先級繼承支持生效。Linux 在沒有鎖爭用的情況下執(zhí)行用戶空間中的所有鎖定。當發(fā)生鎖爭用時,在內(nèi)核空間中進行優(yōu)先級提高和同步隊列管理。

WebSphere VM 使用 POSIX 鎖定 API 來實現(xiàn)我們先前所描述的用于支持優(yōu)先級繼承的核心 Java 語言同步原語。用戶級別的 C 代碼也可以使用這些 POSIX 服務。對于 Java 級別的鎖定操作,分配了一個惟一的 pthread_mutex 并使用原子機器操作將其綁定到 Java 對象。對于 Java 級別的解鎖操作,使用原子操作解除 pthread_mutex 與對象之間的綁定,前提是不存在鎖爭用。存在鎖爭用時,POSIX 鎖定和解鎖操作將觸發(fā) Linux 內(nèi)核優(yōu)先級繼承支持。

為了幫助實現(xiàn)互斥鎖分配和鎖定時間的最小化,JVM 管理一個全局鎖緩存和一個單線程鎖緩存,其中每個緩存包含了未分配的 pthread_mutex。線程專用緩存中的互斥鎖從全局鎖緩存中獲得?;コ怄i在放入線程鎖定緩存之前被線程預先鎖定。非爭用的解鎖操作將一個鎖定的互斥鎖返回給線程鎖定緩存。此處假定以非爭用的鎖定為標準,而 POSIX 級別的鎖定則通過重用預先鎖定的互斥鎖來得到減少和攤銷。

JVM 自身擁有內(nèi)部鎖,用于序列化對關鍵 JVM 資源(如線程列表和全局鎖緩存)的訪問。這些鎖基于優(yōu)先級繼承并且其持有時間較短。





回頁首


RT 應用程序的同步考慮

本節(jié)將介紹 RT 同步的一些特性,這些特性可以幫助移植應用程序的開發(fā)人員使用 RT 線程或編寫新的應用程序以使用 RT 線程化。

普通 Java 線程和 RT 線程之間的鎖爭用

RT 線程可能被普通 Java 線程持有的鎖阻塞。發(fā)生這種情況時,優(yōu)先級繼承接管線程,因此普通 Java 線程的優(yōu)先級被提高到 RT 線程的優(yōu)先級,并且只要它持有鎖就一直保持該優(yōu)先級。普通 Java 線程繼承了 RT 線程的所有調(diào)度特征:

  • 普通 Java 線程按 SCHED_FIFO 策略運行,因此線程不劃分時間片。
  • 在提高了優(yōu)先級的 RT 運行隊列中進行調(diào)度和讓步。

此行為在普通 Java 線程釋放鎖時返回到 SCHED_OTHER。如果 清單 1 中創(chuàng)建的兩個線程在持有 RT 線程所需要的鎖的時候都不運行,則該程序?qū)⒉粫Y束并且出現(xiàn)我們在 使用 RT 線程的問題代碼示例 部分中描述的問題。因為可能出現(xiàn)這種情形,所以對于所有在實時 JVM 中執(zhí)行的線程來說,執(zhí)行 spin 循環(huán)和讓步并不明智。

NHRT 和 RT 線程之間的鎖爭用

NHRT可能在 RT 線程(或相應的普通 Java 線程)持有的鎖處阻塞。雖然 RT 線程持有鎖,但是 GC 可能搶占 RT 并間接地搶占NHRT。NHRT 需要一直等到 RT 不再被 GC 搶占并釋放鎖后才能執(zhí)行。如果 NHRT 執(zhí)行的功能具有嚴格的時間要求,則 GC 搶占NHRT 將是一個嚴重的問題。

WebSphere Real Time中具有確定性的垃圾收集器將暫停時間保持在一毫秒以下,使 NHRT 搶占更具有確定性。如果不能容忍此類暫停,則可以通過避免 NHRT 和 RT線程之間的鎖共享來繞過該問題。如果強制使用鎖定,則可以考慮使用特定于 RT 或 NHRT 的資源和鎖。例如,實現(xiàn)線程入池的應用程序可以考慮對NHRT 和 RT 線程使用分開的池和池鎖。

同樣,javax.realtime 包提供了以下的類:

  • WaitFreeReadQueue 類主要用于將對象從 RT 線程傳遞到 NHRT。
  • WaitFreeWriteQueue 類主要用于將對象從 NHRT 傳遞到 RT 線程。

RT 線程在執(zhí)行無等待操作時可能被 GC 阻塞,上述類保證了 RT 線程在執(zhí)行無等待操作時不會持有 NHRT 所需的鎖。

javax.realtime 包中的同步

某些 javax.realtime 方法故意沒有實現(xiàn)同步,因為即使鎖是無爭用的,同步也會造成系統(tǒng)開銷。如果需要同步,則調(diào)用方負責封裝同步方法或塊中所需的 javax.realtime 方法。編程人員在使用 java.realtime 包的方法時必須考慮添加此類同步。

核心 JLS 包中的同步

相反,如 java.util.Vector 之類的核心 JLS 服務已經(jīng)實現(xiàn)同步。同樣,某些核心 JLS 服務可以執(zhí)行一些內(nèi)部鎖定來序列化某些共享資源。由于這種同步,在使用核心 JLS 服務時,必須謹慎執(zhí)行以避免 GC 搶占 NHRT 的問題(參見 NHRT 和 RT 線程之間的鎖爭用)。

無爭用鎖定的性能

非 RT 應用程序的標準檢查和檢測已表明鎖定主要是無爭用的。無爭用鎖定同樣被認為是 RT 應用程序中的主要類型,特別是現(xiàn)有組件或庫需要重用的時候。如果已知鎖定是無爭用的但是難以避免或刪除同步指令,則對這些鎖定花費一點小的確定的開銷不失為明智的做法。

如前所述,無爭用鎖定操作涉及了一些設置和一個原子機器指令。解鎖操作涉及一個原子機器指令。鎖定操作的設置涉及分配一個預先鎖定的互斥鎖。該分配被認為是無爭用鎖定操作中最大的可變開銷。RealtimeSystem.setMaximumConcurrentLocks() 可以幫助控制這種可變開銷。

RealtimeSystem.setMaximumConcurrentLocks(int numLocks) 使 WebSphere Real Time 中的 VM 將 numLocks 互斥鎖預先分配給全局鎖緩存。全局鎖緩存將其內(nèi)容提供給單線程鎖緩存。通過使用這個 RealTimeSystem API,可以降低具有嚴格時間要求的代碼區(qū)域中發(fā)生鎖定初始化的機率。RealTimeSystem.getMaximumConcurrentLocks() 可以用來幫助決定 setMaximumConcurentLocks() 調(diào)用中應該使用的數(shù)量,但是要注意 getMaximumConcurrentLocks() 提供關于調(diào)用的鎖使用,而不是最高使用標記(high-water mark)。未來的 RTSJ 版本可能提供 API 以便提供最高使用標記。不要為 numLocks 的值提供很大的值,因為調(diào)用 setMaximimConcurrentLocks() 可能耗費過量的時間和內(nèi)存來創(chuàng)建那些鎖。還要注意:這個 API 是定義為 JVM 專用的,因此其他 JVM 可能忽略該調(diào)用或提供不同的行為。

爭用鎖定的性能

一個線程可以同時持有多個鎖,并且可能已經(jīng)按某種順序獲得了這些鎖。所有此類鎖定模式形成了一個鎖層次結構。優(yōu)先級繼承可以指提高和降低一組線程的優(yōu)先級。組中線程的數(shù)量應該不大于系統(tǒng)中最深的鎖層次結構的深度。通過保持較淺的鎖層次結構,可以鎖定最少量的對象,您能夠影響最大量的需要調(diào)整優(yōu)先級的線程。

同步操作中的時間

Object.wait(long timeout, int nanos) 為相關的等待操作提供納秒粒度。HighResolutionTime.waitForObject() API 與 Object.wait() 類似并提供可使用納秒粒度指定的相對時間和絕對時間。在 WebSphere Real Time 中,這兩個 API 都使用底層 POSIX 鎖定等待服務來實現(xiàn)。這些底層服務最多提供微秒粒度。如有需要,出于可移植性目的,javax.realtime 包的 Clock 類的 getResolution() 方法應用于檢索執(zhí)行平臺的分辨率。





回頁首


結束語

RTSJ 通過 javax.realtime包中的新 RT 類和 API 擴展并加強了 Java 編程人員的線程化和同步功能。在 WebSphere Real Time 中,這些功能通過Linux 內(nèi)核的 RT 版本來實現(xiàn),對 POSIX 線程化進行修改并對 JVM 自身進行修改。您現(xiàn)在對 RTSJ線程化和同步有了更深入的了解,這些知識可以幫助您在編寫和部署 RT 應用程序時避免問題的發(fā)生。



參考資料

學習

獲得產(chǎn)品和技術
  • WebSphere Real Time:WebSphere Real Time 利用了標準的 Java 技術并且沒有損失確定性,使應用程序依賴于精確的響應時間。

  • Real-time Java 技術:訪問作者的 IBM alphaWorks 研究站點,查找用于實時 Java 的先進技術。


討論


作者簡介

 

Patrick Gallop 于 1984 年畢業(yè)于 Waterloo 大學并獲得數(shù)學碩士學位。之后不久就加入了 IBM 多倫多實驗室并從事各種編譯器和編譯器工具項目,包括用于不同操作系統(tǒng)和架構的 C 和靜態(tài) Java 編譯器。Patrick 致力于多個版本的 IBM Java 項目開發(fā),最近成為 IBM Real-time Java 項目的高級開發(fā)人員。


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

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
μC/OS
rt-thread的線程調(diào)度與管理
【RT-Thread筆記】臨界區(qū)問題及IPC機制
QNX system architecture 2
linux進程調(diào)度之總章:一些片湯話
Linux中多CPU的runqueue及搶占
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服