Java 虛擬機器允許一個程式有多個 thread 同時在執行。當一個 Java 程式開 始執行時,Java 虛擬機器會自動地產生一個 thread 來執行 main()函式。main()的 執行過程中若呼叫一個 Thread 物件的 start()函式,Java 虛擬機器會自動產生另一 thread 來執行該 Thread 物件之 run()函式。可以產生 thread 的物件,稱之為主動 類別(active class)。循序 Java 程式僅有一個 thread,而在並行 Java 程式中,一個 程式在執行時,會有多個 thread 同時執行,以進行不同的任務。
在 Java 中 , 一 個 類 別 若 是 (1) 繼 承 自 java.lang.Thread 類 別 或 (2) 實 作 java.lang.Runnable 介面,則稱為主動類別。如圖2-6所示。
class SensorThread extends Thread { long time;
SensorThread(long time) { this.time = time;
public static void main(String argv[]) { SensorThread st = new SensorThread(1000);
st.start();
} }
class SensorThread implements Runnable { long time;
SensorThread(long time) { this.time = time;
public static void main(String argv[]) { SensorThread st = new SensorThread(1000);
new Thread(st).start();
} }
圖2-6 並行 Java 程式範例,兩種主動類別
一個 Thread 物件的 start()函式只能被呼叫一次,Thread 物件之 start()函式被 呼叫後會啟動 run()函式執行,假如在 run()函式執行未結束前再次呼叫此 Thread 物件的 start()函式,則會造成 IllegalThreadStateException。若該 thread 已經執行 結束後,該 Thread 物件的 start()函式再次被呼叫,則不會再建立 thread 來執行 run()
程式的測試策略找出其須測試的 paths,因每個 Run 可有多條 paths,path 以 Run 名稱中的字母加上數字編號代稱,如 Run A 的第一條 path 為 A1,第二條為 A2 等等,其圖例如下所示。
圖2-7 Run 與 path 圖例
在 Java 中,兩條 threads 可能是獨立執行的,也可能存在下面三種互動方式:
(1)透過 stop()、suspend()與 resume()函式中止、暫停或繼續另一個 thread,(2)以 join()函式等待某個 thread 執行完畢,(3)呼叫同一個物件的函式以存取共同變 數。其中 stop()函式可能導致錯誤的物件狀態,而呼叫 suspend()及 resume()可能 會導致死結的發生,因而 Java 已不建議使用這些函式。
Java 的 thread 提供了 join()函式,當一個 thread 呼叫另一個 Thread 物件的 join() 函式,若被呼叫的 Thread 物件的 thread 執行 run()尚未結束,則呼叫的 thread 必 須等到被呼叫的 thread 執行完畢之後才能繼續執行;若被呼叫的 Thread 物件之 run()函式已執行完畢,則呼叫 join()函式的 thread 不需等待。此互動方式影響 threads 的執行先後順序,但 threads 之間不會互相影響執行結果。可依循序 Java 程式的測試方法測試各個 Run。
Java 虛擬機器允許多個 thread 並行執行,這些 thread 共用一份共同的記憶體 空間,因此多個 thread 可能會呼叫同一個物件的函式來做溝通,會被多個 thread 同時呼叫使用的物件稱為 shared object。
當不同的 thread 同時呼叫一個 shared object 的函式,是以 interleaving 的方式 執行被呼叫的函式,若這些函式間將存取相同資料成員,則會因執行順序之不同 而產生不一樣的結果,即所謂的 race condition 的問題。為了避免 race condition
Run A A1 A2 A3
發生,Java 程式語言提供了 monitor 作為執行單元之間的同步機制,即同一時間 只有一個執行緒可以執行受 monitor 保護的程式碼。我們可用 lock 的觀念來解釋 Java 所提供的 monitor 機制,在 Java 虛擬機器中每一個物件都有一個對應的 lock,在函式宣告時若加上 synchronized 關鍵字,則此函式被稱為同步函式 (synchronized method),一條 thread 執行一個物件的同步函式前必須取得此物件 的 lock,如果此 lock 已被其他的 thread 取得,則此 thread 將不會執行此同步函 式,直到取得此物件的 lock;當 thread 執行完一個物件同步函式並從同步函式返 回時,此物件相對應的 lock 將被釋放。Java 利用這種機制來保證同時只有一個 thread 在執行 shared object 中的同步函式,避免 race condition 的問題。
建立並行 Java 程式的結構模型可看出各個類別間的關係,而排出類別的測 試順序。一個 shared object 必定會以參數的方式傳遞到主動類別中,因此在做 Java 程式的靜態分析時,可以很輕易地找出會產生 shared object 的共享類別(shared class)。在結構模型圖中以下列方式代表主動類別與共享類別。
class A1 extends Thread { private GC1 gc1;
class A2 extends Thread { private S1 s1;
A2(S1 s) { s1 = s; }
public void main(String argv[]) { a1.start();
class A5 extends Thread { private S2 s21;
A3(S2 s) { s21 = s; }
public void run() { s21.m1(); } }
class A6 extends Thread { private S2 s22;
圖2-10 並行 Java 程式結構模型範例
當一個並行 Java 程式開始執行時,Java 虛擬機器會自動地產生一個 thread 來執行 main()函式的一條 expanded path。若這條 expanded path 沒有呼叫主動類 別的 start()函式,則這條 expanded path 僅是一群循序執行的指令的集合,可用循 序 Java 程式的測試方法測試。
若 main()函式的 expanded path 中有呼叫 Thread 物件的 start()函式的指令,
每一個 start()呼叫會建立一條 thread 來執行這個 Thread 物件 run()函式的一條 expanded path,run()函式中的 expanded path 也可因呼叫別的 Thread 物件的 start() 函式再建立一條 thread,執行被呼叫的 Thread 物件 run()函式的一條 expanded path。main()函式的 expanded path 與其它被建立的 threads 的 expanded paths 會同 時在系統中執行,它們所成的集合,稱為 main()的一條 concurrent path[6]。
以圖2-11為例是一個並行 Java 程式的執行情況,main()函式的 path 依序 呼叫了 Thread 物件 TO1、TO2 與 TO3 的 start()函式,分別建立了三條 thread。
main()的 path 還呼叫了 TO1 的 foo()函式,形成依 1 至 3 循序執行的一條 expanded path。TO1.run()函式的 path 執行標記為 11 的循序指令。TO2.run()與 TO3.run()
GC1 S1
A1 A2
public C1
A3 A4
public C2 GC2
A5 A6
public C3 S2
package A package B package C
函式的 paths 分別呼叫了 shared object SO 的 m1()與 m2()函式,TO2.run()的 expanded path 依 21 至 23 的順序執行,而 TO3.run()的 expanded path 依 31 至 33 的順序執行。圖中以虛線箭頭表示呼叫 Thread 物件的 start()函式建立一條新的 thread。main()的 expanded path 與 TO1.run()、TO2.run()和 TO3.run()的 expanded paths 的集合即為 main()的一條 concurrent path。
圖2-11 並行 Java 程式中同時執行的 expanded paths 範例
執行一個並行 Java 程式,即為執行 main()函式的一條 concurrent path。main() 函式與被建立的 threads 各任取一條 expanded path 的組合,皆可形成一條 concurrent path。找出 main()函式與所有的主動類別 run()函式的 expanded path,
如圖2-12,組合 main()函式與呼叫的所有主動類別的 paths 即可構建該 main() path 的所有 concurrent paths。
main()
圖2-12 expanded path 與建立 thread 示意圖
一個並行 Java 程式被執行時,依據輸入資料不同,可能會執行任何一條 concurrent path,因此測試一個並行 Java 程式正確與否,應驗證所有可能執行的 concurrent path 的執行行為都正確無誤。