ch14 執行緒 (ii)

31
Ch14 Ch14 執執執 執執執 (II) (II) 執執執執執執執執

Upload: anchoret-wright

Post on 02-Jan-2016

101 views

Category:

Documents


3 download

DESCRIPTION

Ch14 執行緒 (II). 物件導向系統實務. 本章大綱. 本章內容包含課本第 16 章 ( 電子書 ) : 行程與執行緒 Thread 類別 Runnable 介面 執行緒的狀態 執行緒控制 執行緒的優先權 多執行緒的同步 執行緒群組. 執行緒的狀態包括 起始狀態 ( Prepared )、 可執行狀態 ( Ready )、 正執行狀態 ( Running )、 等待狀態 ( Waiting )及 死亡狀態 ( Dead )。. 執行緒的狀態. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Ch14  執行緒 (II)

Ch14 Ch14 執行緒執行緒 (II)(II)

物件導向系統實務

Page 2: Ch14  執行緒 (II)

2

本章大綱本章大綱本章內容包含課本第 16章 ( 電子書 ) : 行程與執行緒 Thread類別 Runnable介面 執行緒的狀態 執行緒控制 執行緒的優先權 多執行緒的同步 執行緒群組

Page 3: Ch14  執行緒 (II)

3

執行緒的狀態包括起始狀態( Prepared )、可執行狀態( Ready )、正執行狀態( Running )、等待狀態(Waiting )及死亡狀態( Dead )。

起始狀態(Prepared)

死亡狀態(Dead)

正執行狀態(Running)

等待狀態(Waiting)

可執行狀態(Ready)

排程決定start()

執行緒的狀態

Page 4: Ch14  執行緒 (II)

4

起始狀態( Prepared ):當我們使用 new 關鍵字建立一個Thread 物件後,未呼叫其 start() 方法。可執行狀態( Ready):呼叫 start() 方法後,正等著排程者( thread scheduler)安排執行。正執行狀態( Running ):執行緒正在使用 CPU 的狀態。亦即進入執行 run() 方法等待狀態(Waiting ):在執行 run() 方法的期間,等待某事件的發生才繼續的狀態,例如呼叫 sleep() 方法。

死亡狀態( Dead ):執行緒執行完成 run() 方法後即進入死亡狀態。進入死亡狀態的執行緒不能被重新啟動。

執行緒的狀態

Page 5: Ch14  執行緒 (II)

5

isAlive()isAlive() 方法測試執行緒狀態方法測試執行緒狀態

起始狀態 : isAlive()為 false 可執行狀態 :isAlive()為 true 正執行狀態 :isAlive()為 true 等待狀態 :isAlive()為 true 死亡狀態 :isAlive()為 flase

Page 6: Ch14  執行緒 (II)

6

範例範例 1:1: 利用利用 isAlive()isAlive() 方法來測試執行緒狀態方法來測試執行緒狀態1. class Ch06_012. { public static void main(String []

args)3. { AliveTest at = new AliveTest();4. Thread mt = new Thread( at );5. System.out.println(at.state +

mt.isAlive());6. // 列出執行緒狀態7. mt.start();8. while(mt.isAlive())9. {10. try {11. Thread.sleep(10); }12. catch (Exception e) {}13. System.out.println(at.state +

mt.isAlive());14. }15. } 16. }

1. //AliveTest 類別定義,實作 Runnable 介面2. class AliveTest implements Runnable3. { String state = " 起始狀態 ";4. public void run() // 執行緒方法5. { state = " 可執行狀態 ";6. for(long n=0, m = 0; n<1000000;

n++)7. // 留在可執行狀態的迴圈8. m += n;9. try { state = " 等待狀態 ";10. Thread.sleep(150);// 暫停

150 毫秒11. }12. catch (Exception e) {}13. state = " 死亡狀態 ";14. }15. }

Page 7: Ch14  執行緒 (II)

7

執行緒控制— yield()放棄該次 CPU的使用機會

為了避免 CPU 被獨佔,可以使用 yield()方法讓某個執行緒進入 Ready 狀態,而暫時先讓出 CPU 。

呼叫高優先權執行緒的 yield() 方法,可以讓低優先權執行緒有較多的機會使用到 CPU ,以避免低優先權執行緒處於挨餓狀態( starvation )。

Page 8: Ch14  執行緒 (II)

8

執行緒控制 --等待狀態

執行 sleep() 方法:暫停執行緒一段時間。執行 suspend() 方法:沒有時間限制地暫停執行緒,可呼叫 resume() 方法回到 Ready 狀態。(suspend()和 resume() 已被建議不要使用 )

呼叫 join() 方法:呼叫目標執行緒的 join() 方法,將等到目標執行緒結束之後才會繼續。Input/Output blocked :執行緒進行輸入輸出時,會因為輸入輸出的速度較慢而暫時進入 Waiting 。呼叫同步方法時,未取得物件的 lock 。執行 wait() 方法:放棄物件的使用權並進入等待狀態,可使用 notify() 方法喚醒執行緒。

Page 9: Ch14  執行緒 (II)

9

範例範例 2:2: 使用使用 join()join()1. // 未使用 join(), 主程式會不等其他執行緒 , 自己先完成2. class Ch06_02_013. { public static void main(String [] args) throws

InterruptedException4. { JoinTest jt = new JoinTest();5. jt.start();6. System.out.println(" 已啟動 jt~ ");7. //jt.join(); // 等待 jt 的結束8. System.out.println("\njt 已結束。 ");9. }10. }11. //JoinTest 類別定義,繼承 Thread 類別12. class JoinTest extends Thread13. { public void run() // 執行緒方法14. { for(int i=0; i<30; i++)15. { try {

Thread.sleep(100); }16.

catch(InterruptedException e) {}17. System.out.print('O');18. }19. }20. }

1. // 使用 join(), 主程式會等其他執行緒結束 , 自己才完成2. class Ch06_02_023. { public static void main(String [] args) throws

InterruptedException4. { JoinTest jt = new JoinTest();5. jt.start();6. System.out.println(" 已啟動 jt~ ");7. jt.join(); // 等待 jt 的結束8. System.out.println("\njt 已結束。 ");9. }10. }11. //JoinTest 類別定義,繼承 Thread 類別12. class JoinTest extends Thread13. { public void run() // 執行緒方法14. { for(int i=0; i<30; i++)15. { try {

Thread.sleep(100); }16.

catch(InterruptedException e) {}17. System.out.print('O');18. }19. }20. }

Page 10: Ch14  執行緒 (II)

10

Java 的多執行緒排程是使用簡單的「固定優先權排程」( fixed priority scheduling ),這種排程會依據可執行狀態之執行緒的優先權來決定順序。

子執行緒擁有和父執行緒相同的優先權。

執行緒建立之後,就可以使用 getPriority() 取得優先權值,以 setPriority() 方法來設定優先權值。Thread類別中定義了三個類別常數, NORM_PRIORITY是預設的優先權值,MIN_PRIORITY是最小的優先權值,MAX_PRIORITY為最大的優先權值。

執行緒的優先權

Page 11: Ch14  執行緒 (II)

11

先佔先贏( preemptive scheduling ):高優先權的執行緒可持續執行,直至結束或等待,除非有更高優先權的執行緒出現。 Unix-like 的作業系統,有可能低優先權的沒有機會執行

時間分配( Time slicing ): CPU 的使用被切成小段的時間,執行緒在使用過一單位的 CPU 時間後,就會進入 Ready 狀態,接著排程者會依優先權去挑選下一個執行者。優先權高的,有較大的機會執行。 Windows和MacOS

執行緒的排程實際上是和作業系統有關:

執行緒的優先權

Page 12: Ch14  執行緒 (II)

12

範例範例 33 :測試執行緒的優先權:測試執行緒的優先權

1. class EX12_52. {    public static void main(String [] args) throws InterruptedException3. {   PriorityTest[] p = new PriorityTest[3];4.    Thread[] t = new Thread[p.length];5.    for(int i=0; i<p.length; i++)6.    { p[i] = new PriorityTest();7. t[i] = new Thread( p[i] );    }8.    t[0].setPriority(Thread.MAX_PRIORITY);// 設定為最小優先9.    t[2].setPriority(Thread.MIN_PRIORITY);// 設定為最大優先10.    for(int i=0; i<p.length; i++)11.    { System.out.print( t[i].getPriority() + "\t");12. t[i].start(); // 啟動執行緒 }13.    System.out.println("\n--------------------");14.    for(int i=0; i<10; i++)15.    {   Thread.sleep(1000);16.      System.out.println(p[0].n + "\t" + p[1].n + "\t" + p[2].n);17.     }18.    System.exit(0);19. }}

Page 13: Ch14  執行緒 (II)

13

範例範例 33 :測試執行緒的優先權:測試執行緒的優先權1. //PriorityTest 類別定義,實作 Runnable 介面2. class PriorityTest implements Runnable3. { int n;4. public void run() // 執行緒方法5. { for(int i=0; i<Integer.MAX_VALUE-1;

i++)6. { try {    

Thread.sleep(1); }7.

catch(InterruptedException e) {}8. n++;      }9. }     }

如果 t[0] 的優先權最高

如果 t[0] 的優先權最低

Page 14: Ch14  執行緒 (II)

14

使用共同資源的多執行緒

多執行緒可能造成的錯誤

小明

2,000存入 元小明的媽媽

5,000存入 元

取得存款資料10,000元 取得存款資料

10,000元加總結果12,000元 加總結果

15,000元傳回資料庫

傳回資料庫

資料庫的存款值12,000元 資料庫的存款值

15,000元

Page 15: Ch14  執行緒 (II)

15

範例範例 4:4: 使用共同資源的多執行緒使用共同資源的多執行緒1. class C06_042. { public static void main(String [] args)3. { BankAccount1 小明的帳戶 = new BankAccount1(10000);4. Client1 小明 = new Client1(" 小明 ", 小明的帳戶 , 2000);5. Client1 媽媽 = new Client1(" 媽媽 ", 小明的帳戶 , 5000);6. Thread t1 = new Thread( 小明 );7. Thread t2 = new Thread( 媽媽 );8. System.out.println(" 目前的存款為 " + 小明的帳戶 .check());9. t1.start();10. t2.start();11. } }12. class Client1 implements Runnable// 模擬 ATM 或銀行櫃台作業13. { BankAccount1 ba;14. String name;15. long m;16. Client1(String n, BankAccount1 ba, long m) // 建構子17. { this.name = n;18. this.ba = ba;19. this.m = m; }20. public void run() // 執行緒方法21. { ba.save(name, m); // 呼叫方法將錢存入22. System.out.println(" 存入 "+m+" 後,存款為 "+ba.check());23. } }

Page 16: Ch14  執行緒 (II)

16

範例範例 4:4: 使用共同資源的多執行緒使用共同資源的多執行緒1. class BankAccount12. { private long money; // 存放「錢」的變數3. BankAccount1(long m) // 建構子4. { money = m; // 開戶時存入的錢5. }6. public 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. }

Page 17: Ch14  執行緒 (II)

17

同步化方法

使用 synchronized 關鍵字修飾操作共同資源的方法,可以讓該方法不會被兩個以上的執行緒同時使用,也就是在某個時間頂多只會有一個執行緒在使用該方法。

擁有 synchronized 修飾的方法之物件,就是一個監視器(Monitor ),監視器會讓物件內的synchronized 方法只讓一個執行緒使用。

若物件中的所有方法都使用 sysnchronized 修飾,則在某個時間點只會有一個方法被執行。因為,Monitor是物件而不是方法。

Page 18: Ch14  執行緒 (II)

18

範例範例 5:5: 使用使用 synchronizedsynchronized 來處理同來處理同步步

1. class BankAccount22. { private long money; // 存放「錢」的變數3. BankAccount2(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. }

Page 19: Ch14  執行緒 (II)

19

synchronized敘述

synchronized 也可以當敘述使用

synchronized區塊中的程式敘述還未執行完畢,該物件就不會被其它執行緒所使用。

當 synchronized區塊在執行時,其同步化的物件的鎖( lock )是暫時被取用的。

synchronized (物件 ){

//物件同步區}

Page 20: Ch14  執行緒 (II)

20

synchronized敘述

使用 synchronized修飾方法和下式同義:

synchronized 的同步化對象其實是物件實體,因此synchronized 不能用來修飾屬性、建構子或類別。

方法型別 方法名 (形式參數列 ){

synchronized(this){

//方法內敘述}

}

Page 21: Ch14  執行緒 (II)

21

範例範例 6:6:以以 synchronizedsynchronized 敘述來敘述來 locklock 物物件件

1. class Client3 implements Runnable2. { BankAccount3 ba;3. String name;4. long m;5. Client3(String n, BankAccount3 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. }

Page 22: Ch14  執行緒 (II)

22

wait()及 notify()

wait()、 notify()和 notifyAll()方法只能使用於synchronized修飾的方法或 synchronized敘述中。wait() 方法是讓執行緒進入等待的狀態( waiting pool ),同時「放棄物件的使用權」。

wait()和 sleep() 兩者都可以讓執行緒進入等待狀態,不過 sleep() 並不會放棄物件的使用權。

notify()會隨機叫醒 waiting pool中的某個執行緒,而 nitifyAll()則是叫醒 waiting pool中所有的執行緒。

Page 23: Ch14  執行緒 (II)

23

範例範例 7:7: 使用使用 wait()wait()和和 notify()notify()1. class EX12_92. {3. public static void main(String [] args)4. {5. Dish 盤子 = new Dish();6. Putter 媽媽 = new Putter(盤子 );7. Taker 小強 = new Taker(盤子 );

8. Thread t1 = new Thread( 媽媽 );9. Thread t2 = new Thread( 小強 );10. t1.start();11. t2.start();12. }13. }

Page 24: Ch14  執行緒 (II)

24

1. //Putter 類別,實作 Runnable 介面2. class Putter implements Runnable3. { Dish dish;4. Putter(Dish dish) // 建構子5. { this.dish = dish; }6. public void run() // 執行緒方法7. { try8. { for(int i=1; i<=10; i++)9. { Thread.sleep((int) (Math.random()*1000));// 花費的時間10. dish.put(i); // 放置餅乾11. }12. } catch(InterruptedException e) {}13. } }14. //Taker 類別,實作 Runnable 介面15. class Taker implements Runnable16. { Dish dish;17. Taker(Dish dish) // 建構子18. { this.dish = dish;}19. public void run() // 執行緒方法20. { try21. { for(int i=1; i<=10; i++)22. { Thread.sleep((int) (Math.random()*1000));// 花費的時間23. dish.take(); //拿走餅乾24. }25. } catch(InterruptedException e) {}26. } }

Page 25: Ch14  執行緒 (II)

25

1. //Dish 類別2. class Dish3. { private boolean available = false; // 一開始盤子內沒有餅乾4. private int cakeNum; //餅乾的編號5. public synchronized void put(int n) throws InterruptedException6. { while(available)7. { wait(); }8. cakeNum = n; // 放置新餅乾9. available = true; //盤子內有餅乾10. System.out.println(" 放置第 "+n+" 個餅乾。 ");11. notify(); //提醒,盤內已有餅乾12. }13. public synchronized int take() throws InterruptedException14. { while(!available)15. { wait(); }16. available = false; //拿走餅乾17. System.out.println("拿走第 "+cakeNum+" 個餅乾! ");18. notify(); //提醒,盤內已無餅乾19. return cakeNum;20. }21. }

Page 26: Ch14  執行緒 (II)

26

死結

在使用 wait()及 notify() 時,必須注意避免讓執行緒進行到「等待狀態」之後出不來,如此造成執行緒永遠都在等,而且不會結束,這種情形稱為死結( deadlock )。

死結算是一種邏輯上的錯誤,發生死結時,並不會丟出例外,所以要格外小心避免。

Page 27: Ch14  執行緒 (II)

27

練習二練習二

使用 wait()和 notify撰寫一程式。ShowN型別物件會使用 Printer型別物件的

printN() 方法從1開始列出自然數ShowP型別物件會使用 Printer型別物件的

printP() 方法從 2 開始列出質數自然數和質數必須交替列出

Page 28: Ch14  執行緒 (II)

28

ThreadGroup建構子

ThreadGroup的建構子 說明ThreadGroup(String name) 建立一個名稱為 name的執行緒

群組物件。ThreadGroup(ThreadGroup parent, String name)

在執行緒群組 parent下,建立名為 name的子執行緒群組。

如果程式中有很多執行緒 , 而且有些執行緒的動作是相同時 (如 : 一起啟動 ) ,可以使用執行緒群組

執行緒群組

Page 29: Ch14  執行緒 (II)

29

執行緒群組可以包含子群組和執行緒

群組A

子群組C子群組B

子群組D

執行緒3

執行緒1

執行緒2

執行緒4

執行緒5

執行緒6

執行緒7

執行緒8

執行緒9

執行緒10

執行緒群組

Page 30: Ch14  執行緒 (II)

30

和執行緒群組相關的 Thread 建構子

執行緒加入群組的建構子

說明

Thread(ThreadGroup group, Runnable target)

以實作 Runnable介面的 target物件建立執行緒,並將執行緒歸於group群組。

Thread(ThreadGroup group, Runnable target, String name)

以實作 Runnable介面的 target物件建立名為 name的執行緒,並將執行緒歸於 group群組。

Thread(ThreadGroup group, String name)

建立名為 name的執行緒,並將執行緒歸於 group群組。

執行緒群組

Page 31: Ch14  執行緒 (II)

31

常用的 ThreadGroup方法

ThreadGroup的方法 說明final int getMaxPriority() 取得群組中最大的優先權值。final String getName() 取得群組名稱。final ThreadGroup getParent() 取得父群組。final void interrupt() 中斷群組中所有的執行緒。final void setMaxPriority(int pri) 設定群組的最高優先權值為

pri。

執行緒群組