1 / 31

Ch14 執行緒 (II)

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

Download Presentation

Ch14 執行緒 (II)

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Ch14 執行緒(II) 物件導向系統實務

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

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

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

  5. isAlive()方法測試執行緒狀態 • 起始狀態: isAlive()為false • 可執行狀態:isAlive()為true • 正執行狀態:isAlive()為true • 等待狀態:isAlive()為true • 死亡狀態:isAlive()為flase

  6. class Ch06_01 { public static void main(String [] args) { AliveTest at = new AliveTest(); Thread mt = new Thread( at ); System.out.println(at.state + mt.isAlive()); //列出執行緒狀態 mt.start(); while(mt.isAlive()) { try { Thread.sleep(10); } catch (Exception e) {} System.out.println(at.state + mt.isAlive()); } } } //AliveTest類別定義,實作Runnable介面 class AliveTest 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 = "死亡狀態 "; } } 範例1:利用isAlive()方法來測試執行緒狀態

  7. 為了避免CPU被獨佔,可以使用yield()方法讓某個執行緒進入Ready狀態,而暫時先讓出CPU。為了避免CPU被獨佔,可以使用yield()方法讓某個執行緒進入Ready狀態,而暫時先讓出CPU。 呼叫高優先權執行緒的yield()方法,可以讓低優先權執行緒有較多的機會使用到CPU,以避免低優先權執行緒處於挨餓狀態(starvation)。 執行緒控制—yield()放棄該次CPU的使用機會

  8. 執行sleep()方法:暫停執行緒一段時間。 執行suspend()方法:沒有時間限制地暫停執行緒,可呼叫resume()方法回到Ready狀態。(suspend()和resume()已被建議不要使用) 呼叫join()方法:呼叫目標執行緒的join()方法,將等到目標執行緒結束之後才會繼續。 Input/Output blocked:執行緒進行輸入輸出時,會因為輸入輸出的速度較慢而暫時進入Waiting。 呼叫同步方法時,未取得物件的lock。 執行wait()方法:放棄物件的使用權並進入等待狀態,可使用notify()方法喚醒執行緒。 執行緒控制--等待狀態

  9. //未使用join(),主程式會不等其他執行緒,自己先完成//未使用join(),主程式會不等其他執行緒,自己先完成 class Ch06_02_01 { public static void main(String [] args) throws InterruptedException { JoinTest jt = new JoinTest(); jt.start(); System.out.println("已啟動jt~"); //jt.join(); //等待jt的結束 System.out.println("\njt已結束。"); } } //JoinTest類別定義,繼承Thread類別 class JoinTest extends Thread { public void run() //執行緒方法 { for(int i=0; i<30; i++) { try { Thread.sleep(100); } catch(InterruptedException e) {} System.out.print('O'); } } } //使用join(),主程式會等其他執行緒結束,自己才完成 class Ch06_02_02 { public static void main(String [] args) throws InterruptedException { JoinTest jt = new JoinTest(); jt.start(); System.out.println("已啟動jt~"); jt.join(); //等待jt的結束 System.out.println("\njt已結束。"); } } //JoinTest類別定義,繼承Thread類別 class JoinTest extends Thread { public void run() //執行緒方法 { for(int i=0; i<30; i++) { try { Thread.sleep(100); } catch(InterruptedException e) {} System.out.print('O'); } } } 範例2:使用join()

  10. Java的多執行緒排程是使用簡單的「固定優先權排程」(fixed priority scheduling),這種排程會依據可執行狀態之執行緒的優先權來決定順序。 子執行緒擁有和父執行緒相同的優先權。 執行緒建立之後,就可以使用getPriority()取得優先權值,以setPriority()方法來設定優先權值。 Thread類別中定義了三個類別常數,NORM_PRIORITY是預設的優先權值,MIN_PRIORITY是最小的優先權值,MAX_PRIORITY為最大的優先權值。 執行緒的優先權

  11. 先佔先贏(preemptive scheduling):高優先權的執行緒可持續執行,直至結束或等待,除非有更高優先權的執行緒出現。Unix-like的作業系統,有可能低優先權的沒有機會執行 時間分配(Time slicing):CPU的使用被切成小段的時間,執行緒在使用過一單位的CPU時間後,就會進入Ready狀態,接著排程者會依優先權去挑選下一個執行者。優先權高的,有較大的機會執行。Windows和MacOS 執行緒的優先權 • 執行緒的排程實際上是和作業系統有關:

  12. 範例3:測試執行緒的優先權 • class EX12_5 • {public static void main(String [] args) throws InterruptedException • {PriorityTest[] p = new PriorityTest[3]; • Thread[] t = new Thread[p.length]; • for(int i=0; i<p.length; i++) • { p[i] = new PriorityTest(); • t[i] = new Thread( p[i] );} • t[0].setPriority(Thread.MAX_PRIORITY);//設定為最小優先 • t[2].setPriority(Thread.MIN_PRIORITY);//設定為最大優先 • for(int i=0; i<p.length; i++) • { System.out.print( t[i].getPriority() + "\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); • }}

  13. //PriorityTest類別定義,實作Runnable介面 class PriorityTest implements Runnable { int n; public void run() //執行緒方法 { for(int i=0; i<Integer.MAX_VALUE-1; i++) { try {Thread.sleep(1); } catch(InterruptedException e) {} n++;} }} 如果t[0]的優先權最高 如果t[0]的優先權最低 範例3:測試執行緒的優先權

  14. 多執行緒可能造成的錯誤 使用共同資源的多執行緒

  15. 範例4:使用共同資源的多執行緒 • class C06_04 • { public static void main(String [] args) • { BankAccount1 小明的帳戶 = new BankAccount1(10000); • Client1 小明 = new Client1("小明", 小明的帳戶, 2000); • Client1 媽媽 = new Client1("媽媽", 小明的帳戶, 5000); • Thread t1 = new Thread(小明); • Thread t2 = new Thread(媽媽); • System.out.println("目前的存款為" + 小明的帳戶.check()); • t1.start(); • t2.start(); • } } • class Client1 implements Runnable//模擬ATM或銀行櫃台作業 • { BankAccount1 ba; • String name; • long m; • Client1(String n, BankAccount1 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()); • } }

  16. 範例4:使用共同資源的多執行緒 • class BankAccount1 • { private long money; //存放「錢」的變數 • BankAccount1(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; • } • }

  17. 使用synchronized關鍵字修飾操作共同資源的方法,可以讓該方法不會被兩個以上的執行緒同時使用,也就是在某個時間頂多只會有一個執行緒在使用該方法。使用synchronized關鍵字修飾操作共同資源的方法,可以讓該方法不會被兩個以上的執行緒同時使用,也就是在某個時間頂多只會有一個執行緒在使用該方法。 擁有synchronized修飾的方法之物件,就是一個監視器(Monitor),監視器會讓物件內的synchronized方法只讓一個執行緒使用。 若物件中的所有方法都使用sysnchronized修飾,則在某個時間點只會有一個方法被執行。因為,Monitor是物件而不是方法。 同步化方法

  18. 範例5:使用synchronized來處理同步 • class BankAccount2 • { private long money; //存放「錢」的變數 • BankAccount2(long m) //建構子 • { money = m; //開戶時存入的錢 • } • public synchronized 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; • } • }

  19. synchronized也可以當敘述使用 synchronized區塊中的程式敘述還未執行完畢,該物件就不會被其它執行緒所使用。 當synchronized區塊在執行時,其同步化的物件的鎖(lock)是暫時被取用的。 synchronized敘述 synchronized (物件) { //物件同步區 }

  20. 使用synchronized修飾方法和下式同義: synchronized的同步化對象其實是物件實體,因此synchronized不能用來修飾屬性、建構子或類別。 synchronized敘述 方法型別 方法名(形式參數列) { synchronized(this) { //方法內敘述 } }

  21. 範例6:以synchronized敘述來lock物件 • class Client3 implements Runnable • { BankAccount3 ba; • String name; • long m; • Client3(String n, BankAccount3 ba, long m) //建構子 • { this.name = n; • this.ba = ba; • this.m = m; • } • public void run() //執行緒方法 • { synchronized (ba) • { ba.save(name, m); //呼叫方法將錢存入 • System.out.println(“存入 ”+m+“後,存款為 "+ba.check()); • } • } • }

  22. wait()、notify()和notifyAll()方法只能使用於synchronized修飾的方法或synchronized敘述中。wait()、notify()和notifyAll()方法只能使用於synchronized修飾的方法或synchronized敘述中。 wait()方法是讓執行緒進入等待的狀態(waiting pool),同時「放棄物件的使用權」。 wait()和sleep()兩者都可以讓執行緒進入等待狀態,不過sleep()並不會放棄物件的使用權。 notify()會隨機叫醒waiting pool中的某個執行緒,而nitifyAll()則是叫醒waiting pool中所有的執行緒。 wait()及notify()

  23. 範例7:使用wait()和notify() • class EX12_9 • { • public static void main(String [] args) • { • Dish 盤子 = new Dish(); • Putter 媽媽 = new Putter(盤子); • Taker 小強 = new Taker(盤子); • Thread t1 = new Thread(媽媽); • Thread t2 = new Thread(小強); • t1.start(); • t2.start(); • } • }

  24. //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(InterruptedException 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(InterruptedException e) {} • } }

  25. //Dish類別 • class Dish • { private boolean available = false; //一開始盤子內沒有餅乾 • private int cakeNum; //餅乾的編號 • public synchronized void put(int n) throws InterruptedException • { while(available) • { wait(); } • cakeNum = n; //放置新餅乾 • available = true; //盤子內有餅乾 • System.out.println("放置第 "+n+" 個餅乾。"); • notify(); //提醒,盤內已有餅乾 • } • public synchronized int take() throws InterruptedException • { while(!available) • { wait(); } • available = false; //拿走餅乾 • System.out.println("拿走第 "+cakeNum+" 個餅乾!"); • notify(); //提醒,盤內已無餅乾 • return cakeNum; • } • }

  26. 在使用wait()及notify()時,必須注意避免讓執行緒進行到「等待狀態」之後出不來,如此造成執行緒永遠都在等,而且不會結束,這種情形稱為死結(deadlock)。在使用wait()及notify()時,必須注意避免讓執行緒進行到「等待狀態」之後出不來,如此造成執行緒永遠都在等,而且不會結束,這種情形稱為死結(deadlock)。 死結算是一種邏輯上的錯誤,發生死結時,並不會丟出例外,所以要格外小心避免。 死結

  27. 練習二 使用wait()和notify撰寫一程式。 ShowN型別物件會使用Printer型別物件的printN()方法從1開始列出自然數 ShowP型別物件會使用Printer型別物件的printP()方法從2開始列出質數 自然數和質數必須交替列出

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

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

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

  31. 常用的ThreadGroup方法 執行緒群組

More Related