469246d1479df6575d0c8b52d3df9566.ppt
- Количество слайдов: 31
Ch 14 執行緒 (II) 物件導向系統實務
本章大綱 本章內容包含課本第 16章(電子書): n 行程與執行緒 n Thread類別 n Runnable介面 n 執行緒的狀態 n 執行緒控制 n 執行緒的優先權 n 多執行緒的同步 n 執行緒群組 2
執行緒的狀態包括 起始狀態 ( Prepared)、 可執 行狀態 ( Ready)、 正執行狀態 ( Running)、 等待 狀態 ( Waiting)及 死亡狀態 ( Dead)。 3
執行緒的狀態 起始狀態 ( Prepared):當我們使用 new關鍵字建立一個 Thread物件後,未呼叫其 start()方法。 可執行狀態 ( Ready): 叫 start()方法後,正等著排程者 呼 ( thread scheduler)安排執行。 正執行狀態 ( Running):執行緒正在使用CPU的狀態。亦 即進入執行 run()方法 等待狀態 ( Waiting):在執行 run()方法的期間,等待某事 件的發生才繼續的狀態,例如呼叫 sleep()方法。 死亡狀態 ( Dead):執行緒執行完成run()方法後即進入 死亡狀態。進入死亡狀態的執行緒不能被重新啟動。 4
is. Alive()方法測試執行緒狀態 is. Alive()為false n 可執行狀態: is. Alive()為true n 正執行狀態: is. Alive()為true n 等待狀態: is. Alive()為true n 死亡狀態: is. Alive()為flase n 起始狀態: 5
範例1: 利用is. Alive()方法來測試執行緒狀態 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. class Ch 06_01 { public static void main(String [] args) { Alive. Test at = new Alive. Test(); Thread mt = new Thread( at ); System. out. println(at. state + mt. is. Alive()); //列出執行緒狀態 mt. start(); while(mt. is. Alive()) { try { Thread. sleep(10); } catch (Exception e) {} System. out. println(at. state + mt. is. Alive()); } } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. //Alive. Test類別定義,實作 Runnable介面 class Alive. Test implements Runnable { String state = "起始狀態 "; public void run() //執行緒方法 { state = "可執行狀態 "; for(long n=0, m = 0; n<1000000; n++) //留在可執行狀態的迴圈 m += n; try { state = "等待狀態 "; Thread. sleep(150); //暫停 150毫秒 } catch (Exception e) {} state = "死亡狀態 "; } } 6
執行緒控制 —yield()放棄該次 CPU的使用機會 為了避免 CPU被獨佔,可以使用 yield()方法讓 某個執行緒進入 Ready狀態,而暫時先讓出 CPU。 呼叫高優先權執行緒的 yield()方法,可以讓低優 先權執行緒有較多的機會使用到 CPU,以避免 低優先權執行緒處於挨餓狀態( starvation)。 7
執行緒控制 --等待狀態 執行 sleep()方法 :暫停執行緒一段時間。 執行 suspend()方法 :沒有時間限制地暫停執行 緒,可呼叫 resume()方法回到 Ready狀態。 (suspend()和 resume()已被建議不要使用 ) 呼叫 join()方法 :呼叫目標執行緒的 join()方法,將 等到目標執行緒結束之後才會繼續。 Input/Output blocked:執行緒進行輸入輸出時, 會因為輸入輸出的速度較慢而暫時進入 Waiting。 呼叫同步方法時,未取得物件的 lock。 執行 wait()方法 :放棄物件的使用權並進入等待 狀態,可使用 notify()方法喚醒執行緒。 8
範例2: 使用join() 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. //未使用 join(), 主程式會不等其他執行緒 , 自己先完成 class Ch 06_02_01 { public static void main(String [] args) throws Interrupted. Exception { Join. Test jt = new Join. Test(); jt. start(); System. out. println("已啟動 jt~ "); //jt. join(); //等待 jt的結束 System. out. println("njt已結束。"); } } //Join. Test類別定義,繼承 Thread類別 class Join. Test extends Thread { public void run() //執行緒方法 { for(int i=0; i<30; i++) { try { Thread. sleep(100); } 16. 17. 18. 19. 20. catch(Interrupted. Exception e) {} System. out. print('O'); } } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. //使用 join(), 主程式會等其他執行緒結束 , 自己才完成 class Ch 06_02_02 { public static void main(String [] args) throws Interrupted. Exception { Join. Test jt = new Join. Test(); jt. start(); System. out. println("已啟動 jt~ "); jt. join(); //等待 jt的結束 System. out. println("njt已結束。"); } } //Join. Test類別定義,繼承 Thread類別 class Join. Test extends Thread { public void run() //執行緒方法 { for(int i=0; i<30; i++) { try { Thread. sleep(100); } 16. 17. 18. 19. 20. catch(Interrupted. Exception e) {} System. out. print('O'); } } } 9
執行緒的優先權 Java的多執行緒排程是使用簡單的「固定優先 權排程」( fixed priority scheduling),這種排程會 依據可執行狀態之執行緒的優先權來決定順序。 子執行緒擁有和父執行緒相同的優先權。 執行緒建立之後,就可以使用 get. Priority()取得 優先權值,以 set. Priority()方法來設定優先權值。 Thread類別中定義了三個類別常數, NORM_PRIORITY是預設的優先權值, MIN_PRIORITY是最小的優先權值, MAX_PRIORITY為最大的優先權值。 10
執行緒的優先權 執行緒的排程實際上是和作業系統有 關: 先佔先贏 ( preemptive scheduling) :高優先權的 執行緒可持續執行,直至結束或等待,除非有 更高優先權的執行緒出現。 Unix-like的作業系統, 有可能低優先權的沒有機會執行 時間分配 ( Time slicing) CPU的使用被切成小 : 段的時間,執行緒在使用過一單位的 CPU時間 後,就會進入 Ready狀態,接著排程者會依優先 權去挑選下一個執行者。優先權高的,有較大 的機會執行。 Windows和 Mac. OS 11
範例3:測試執行緒的優先權 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. class EX 12_5 { public static void main(String [] args) throws Interrupted. Exception { Priority. Test[] p = new Priority. Test[3]; Thread[] t = new Thread[p. length]; for(int i=0; i<p. length; i++) { p[i] = new Priority. Test(); t[i] = new Thread( p[i] ); } t[0]. set. Priority(Thread. MAX_PRIORITY); //設定為最小優先 t[2]. set. Priority(Thread. MIN_PRIORITY); //設定為最大優先 for(int i=0; i<p. length; i++) { System. out. print( t[i]. get. Priority() + "t"); t[i]. start(); //啟動執行緒 } System. out. println("n----------"); for(int i=0; i<10; i++) { Thread. sleep(1000); System. out. println(p[0]. n + "t" + p[1]. n + "t" + p[2]. n); } System. exit(0); }} 12
範例3:測試執行緒的優先權 1. 2. 3. 4. 5. 6. 7. 8. 9. //Priority. Test類別定義,實作 Runnable介面 class Priority. Test implements Runnable { int n; public void run() //執行緒方法 { for(int i=0; i<Integer. MAX_VALUE-1; i++) { try { Thread. sleep(1); } catch(Interrupted. Exception e) {} n++; } } } n 如果 t[0]的優先權最 高 n 如果 t[0]的優先權最 低 13
使用共同資源的多執行緒可能造成的錯誤 14
範例4: 使用共同資源的多執行緒 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. class C 06_04 { public static void main(String [] args) { Bank. Account 1 小明的帳戶 = new Bank. Account 1(10000); Client 1 小明 = new Client 1("小明", 小明的帳戶, 2000); Client 1 媽媽 = new Client 1("媽媽", 小明的帳戶, 5000); Thread t 1 = new Thread(小明); Thread t 2 = new Thread(媽媽); System. out. println("目前的存款為" + 小明的帳戶. check()); t 1. start(); t 2. start(); } } class Client 1 implements Runnable//模擬ATM或銀行櫃台作業 { Bank. Account 1 ba; String name; long m; Client 1(String n, Bank. Account 1 ba, long m) //建構子 { this. name = n; this. ba = ba; this. m = m; } public void run() //執行緒方法 { ba. save(name, m); //呼叫方法將錢存入 System. out. println("存入 "+m+" 後,存款為 "+ba. check()); 15
範例4: 使用共同資源的多執行緒 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. class Bank. Account 1 { private long money; //存放「錢」的變數 Bank. Account 1(long m) //建構子 { money = m; //開戶時存入的錢 } public void save(String s, long m) //存入錢的方法 { System. out. println(s+"開始存入"+m); long tmp = money; //前取得目前存款 for(long x=0; x<10000000; x++); //模擬存入所花費的時間 money = tmp + m; //相加之後存回 } public long check() //取得目前存款的方法 { return money; } } 16
同步化方法 使用 synchronized關鍵字修飾 操作共同資源的 方法 ,可以讓該 方法 不會被兩個以上的執行緒 同時使用,也就是在某個時間頂多只會有一個 執行緒在使用該方法。 擁有 synchronized修飾的方法之物件,就是一個 監視器 ( Monitor),監視器會讓物件內的 synchronized方法只讓一個執行緒使用。 若物件中的所有方法都使用 sysnchronized修飾, 則在某個時間點只會有一個方法被執行。因為, Monitor是物件而不是方法 。 17
範例5: 使用synchronized來處理同步 1. class Bank. Account 2 2. { private long money; //存放「錢」的變數 3. Bank. Account 2(long m) //建構子 4. { money = m; //開戶時存入的錢 5. } 6. public synchronized void save(String s, long m)//存入錢的方法 7. { System. out. println(s+"開始存入"+m); 8. long tmp = money; //前取得目前存款 9. for(long x=0; x<10000000; x++); //模擬存入所花費的時間 10. money = tmp + m; //相加之後存回 11. } 12. public long check() //取得目前存款的方法 13. { return money; 14. } 15. } 18
synchronized敘述 synchronized也可以當敘述使用 synchronized (物件 ) { //物件同步區 } synchronized區塊中的程式敘述還未執行完畢, 該物件就不會被其它執行緒所使用。 當 synchronized區塊在執行時,其同步化的物件 的鎖( lock)是暫時被取用的。 19
synchronized敘述 使用 synchronized修飾方法和下式同義: 方法型別 方法名 (形式參數列 ) { synchronized(this) { //方法內敘述 } } synchronized的同步化對象其實是物件實體,因 此 synchronized不能用來修飾 屬性 、 建構子 或 類 別。 20
範例6: 以synchronized敘述來lock物件 1. class Client 3 implements Runnable 2. { Bank. Account 3 ba; 3. String name; 4. long m; 5. Client 3(String n, Bank. Account 3 ba, long m) //建構子 6. { this. name = n; 7. this. ba = ba; 8. this. m = m; 9. } 10. public void run() //執行緒方法 11. { synchronized (ba) 12. { ba. save(name, m); //呼叫方法將錢存入 13. System. out. println(“存入 ”+m+“ 後,存款為 "+ba. check()); 14. 15. 16. } } } 21
wait()及 notify() wait()、 notify()和 notify. All()方法只能使用於 synchronized修飾的方法或 synchronized敘述中。 wait()方法是讓執行緒進入等待的狀態( waiting pool),同時「放棄物件的使用權」。 wait()和 sleep()兩者都可以讓執行緒進入等待狀 態,不過sleep()並不會放棄物件的使用權。 notify()會隨機叫醒 waiting pool中的某個執行緒, 而 nitify. All()則是叫醒 waiting pool中所有的執行緒。 22
範例7: 使用wait()和notify() 1. 2. 3. 4. 5. 6. 7. class EX 12_9 { public static void main(String [] args) { Dish 盤子 = new Dish(); Putter 媽媽 = new Putter(盤子); Taker 小強 = new Taker(盤子); 8. 9. 10. 11. 12. 13. } Thread t 1 = new Thread(媽媽); Thread t 2 = new Thread(小強); t 1. start(); t 2. start(); } 23
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. //Putter類別,實作Runnable介面 class Putter implements Runnable { Dish dish; Putter(Dish dish) //建構子 { this. dish = dish; } public void run() //執行緒方法 { try { for(int i=1; i<=10; i++) { Thread. sleep((int) (Math. random()*1000)); //花費的時間 dish. put(i); //放置餅乾 } } catch(Interrupted. Exception e) {} } } //Taker類別,實作Runnable介面 class Taker implements Runnable { Dish dish; Taker(Dish dish) //建構子 { this. dish = dish; } public void run() //執行緒方法 { try { for(int i=1; i<=10; i++) { Thread. sleep((int) (Math. random()*1000)); //花費的時間 dish. take(); //拿走餅乾 } } catch(Interrupted. Exception e) {} } } 24
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. //Dish類別 class Dish { private boolean available = false; //一開始盤子內沒有餅乾 private int cake. Num; //餅乾的編號 public synchronized void put(int n) throws Interrupted. Exception { while(available) { wait(); } cake. Num = n; //放置新餅乾 available = true; //盤子內有餅乾 System. out. println("放置第 "+n+" 個餅乾。"); notify(); //提醒,盤內已有餅乾 } public synchronized int take() throws Interrupted. Exception { while(!available) { wait(); } available = false; //拿走餅乾 System. out. println("拿走第 "+cake. Num+" 個餅乾!"); notify(); //提醒,盤內已無餅乾 return cake. Num; } } 25
死結 在使用 wait()及 notify()時,必須注意避免讓執行 緒進行到「等待狀態 」之後出不來,如此造成執 行緒永遠都在等,而且不會結束,這種情形稱 為 死結 ( deadlock)。 死結算是一種 邏輯上的錯誤 ,發生死結時,並 不會丟出例外,所以要格外小心避免。 26
練習二 使用wait()和notify撰寫一程式。 Show. N型別物件會使用Printer型別物件的 print. N()方法從1開始列出自然數 Show. P型別物件會使用Printer型別物件的 print. P()方法從 2開始列出質數 自然數和質數必須交替列出 27
執行緒群組 如果程式中有很多執行緒 , 而且有些執行緒的 動作是相同時 (如 : 一起啟動 ),可以使用執行緒 群組 Thread. Group建構子 Thread. Group的建構子 Thread. Group(String name) Thread. Group(Thread. Group parent, String name) 說明 建立一個名稱為 name的執行 緒群組物件。 在執行緒群組 parent下,建立 名為 name的子執行緒群組。 28
執行緒群組可以包含子群組和執行緒 29
執行緒群組 和執行緒群組相關的 Thread建構子 執行緒加入群組的建 說明 構子 Thread(Thread. Group 以實作 Runnable介面的 target物 group, Runnable target) 件建立執行緒,並將執行緒歸 於 group群組。 Thread(Thread. Group group, Runnable target, String name) Thread(Thread. Group group, String name) 以實作 Runnable介面的 target物 件建立名為 name的執行緒,並 將執行緒歸於 group群組。 建立名為 name的執行緒,並將 執行緒歸於 group群組。 30
執行緒群組 常用的 Thread. Group方法 Thread. Group的方法 final int get. Max. Priority() 說明 取得群組中最大的優先權 值。 final String get. Name() 取得群組名稱。 final Thread. Group get. Parent() 取得父群組。 final void interrupt() 中斷群組中所有的執行緒。 final void set. Max. Priority(int pri) 設定群組的最高優先權值 為 pri。 31
469246d1479df6575d0c8b52d3df9566.ppt