Ch13 執行緒(I) 物件導向系統實務
本章大綱 本章內容包含課本第16章(電子書): 行程與執行緒 Thread類別 Runnable介面 執行緒的狀態 執行緒控制 執行緒的優先權 多執行緒的同步 執行緒群組
多工與多執行緒 目前的作業系統都強調「多工」(Multitasking)。例如:微軟的Windows作業系統屬於一套多工的作業系統,可以同時執行小畫家、記事本和小算盤等多個應用程式。 不同於作業系統的多工,「多執行緒」(Multithreaded)是指在單一應用程式擁有多個執行流程。例如:Web瀏覽程式可以在下載網頁檔案的同時顯示動畫、播放音樂或捲動視窗瀏覽網頁內容。
何謂多工-行程(process) 在OS中,正在執行的應用程式為一個行程 多個行程使用單一CPU
Java的執行緒-說明 「執行緒」(Thread)也稱為「輕量行程」(Lightweight Process),其執行過程類似上述程式執行,只是執行緒不能單獨存在或執行,一定需要隸屬於一個程式,由程式啟動執行緒,如右圖所示:
Java的執行緒-多執行緒 如果程式碼本身沒有先後依存關係(例如:沒有共用同一個陣列或變數),程式能夠分割成多個同步執行緒來一起執行,這種程式設計方法稱為「平行程式設計」(Parallel Programming),如右圖所示:
執行緒的優點 多行程多執行緒使用單一CPU
執行緒的優點 不同的行程所佔有的記憶體資源不同,各自獨立互不干擾。 大部份的個人電腦只有一個CPU,所有行程都必須透過系統取得CPU的使用權。 每個行程輪流使用CPU的情形就像是每個行程都同時執行一樣。 執行緒是行程中的子程式,這些子程式共享行程內的資源,同時時可分配CPU的使用權,以提高CPU使用率
JAVA的執行緒說明 main()其實就是Java應用程式的預設執行緒(或稱主執行緒,Main Thread)。 多執行緒程式必須靠主執行緒去啟動其它執行緒的進行。 Java的執行緒都必須是java.lang.Thread類別的物件。
Java執行緒的使用方法 Java執行緒是建立Thread類別的物件,一共有2種方式建立多執行緒應用程式,如下所示: 方法2:實作Runnable介面,如果你的類別已經繼承其它類別(例如:已繼承JFrame)時,可以採用此方法,利用實作介面來達成執行緒。
方法1:繼承Thread類別-架構 若類別沒有繼承其它類別,就可以直接繼承Thread類別,然後覆寫run()方法建立執行緒物件,如下所示: class UserThread extends Thread { // 建構子 public UserThread(int length, String name) { } // 執行執行緒 public void run() { ……… } }
方法1:繼承Thread類別-啟動 接著就可以建立Thread物件,啟動執行緒,如下所示: UserThread ut1 = new UserThread(5, "執行緒A"); ut1.start(); 上述程式碼在建立好UserThread物件ut1後,這是Thread物件,所以可以直接使用start()方法啟動執行緒。
Thread 類別 Thread的建構子及啟動、暫停方法 Thread之建構子及方法 說明 Thread(String name) final String getName() 取得執行緒的名稱。 static void sleep(long ms) throws InterruptedException 讓執行緒暫停ms毫秒的時間(若暫停的期間執行緒被中斷,則會丟出InterruptedException例外)。 void run() 執行緒啟動時所執行的方法。 void start() 讓執行緒啟動的方法。
Thread 類別 使用執行緒的步驟: Step 1: 先宣告一Thread的子類別,並建立一run() Step 3: 呼叫start()方法來啟動執行緒 Step 4: 在Step 3啟動時,會自動開始執行run()方 法
範例1:利用Thread來建立多執行緒(1/3) 主程式 class Ch05_01 { public static void main(String args []) MyThread05_01 t1 = new MyThread05_01("執行緒A:"); MyThread05_01 t2 = new MyThread05_01("執行緒B:"); System.out.println("開始執行執行緒..."); t1.start(); t2.start(); try { Thread.sleep((int)(Math.random()*1000)); } catch(InterruptedException e) {} System.out.println("main主執行緒結束..."); }
範例1:利用Thread來建立多執行緒(2/3) class MyThread05_01 extends Thread { //建構子 MyThread05_01(String name) { super(name); } public void run() { for(int i = 0; i<= 5; i++) { int restTime = (int)(Math.random()*1000); String s = getName() + "迴圈執行第" + i + "次! 要休息" + restTime + "毫秒!"; System.out.println(s); try { Thread.sleep(restTime); } catch(InterruptedException e) {}
範例1:利用Thread來建立多執行緒(3/3) 第1次執行結果: 第2次執行結果:
Thread 類別 欲啟動一個執行緒時,必須呼叫它的start()方法,而不是直接呼叫它的run()方法。 若直接呼叫Thread類別物件的run()方法,並不會啟動執行緒,只是一般方法的呼叫。 執行緒結束之後,執行緒物件還存在,但是不可以重新啟動。
Thread 類別 呼叫start()方法後執行緒才開始執行
方法2:Runnable介面 執行緒欲繼承Thread以外的類別時,可以利用Runnable介面建立。 Runnable介面只宣告一個run()方法,所以實作介面時只要實作run()方法即可。 實作Runnable介面的類別,還是必須依賴Thread類別的建構子才能建立一個執行緒物件。
方法2:實作Runnable介面-架構 使用時機:Swing應用程式繼承自JFrame,Java Applet繼承自JApplet,就只能實作Runnable介面的run()方法來建立多執行緒的應用程式,如下所示: class UserThread extends UserClass implements Runnable { // 建構子 public UserThread(int length) { } // 執行執行緒 public void run() { ……… } }
方法2: 實作Runnable介面-啟動 接著可以建立Thread物件和啟動執行緒,如下所示: UserThread ut1 = new UserThread(5); Thread t1 = new Thread(ut1, "執行緒A"); t1.start(); 上述程式碼在建立好UserThread物件ut1後,使用ut1物件建立Thread物件的執行緒,參數字串是執行緒名稱,最後使用start()方法啟動執行緒。
方法2: 實作Runnable介面- Thread建構子
範例2:使用Runnable介面(1/3) Ch05_02.java主程式 class Ch05_02 { public static void main(String args []) MyRunnable05_02 r1 = new MyRunnable05_02(); MyRunnable05_02 r2 = new MyRunnable05_02(); Thread t1 = new Thread(r1, "執行緒A"); Thread t2 = new Thread(r2, "執行緒B"); System.out.println("開始執行執行緒..."); t1.start(); t2.start(); try { Thread.sleep((int)(Math.random()*1000)); } catch(InterruptedException e) {} System.out.println("main主執行緒結束..."); }
範例2:使用Runnable介面(2/3) 繼承Runnable介面的自訂類別 class MyRunnable05_02 implements Runnable { public void run() for(int i = 0; i<= 5; i++) int restTime = (int)(Math.random()*1000); String s = Thread.currentThread().getName() + "迴圈執行第" + i + "次! 要休息" + restTime + "毫秒!"; System.out.println(s); try { Thread.sleep(restTime); } catch(InterruptedException e) {} }
範例2:使用Runnable介面(3/3) 第一次執行結果 第二次行結果
練習一 二個執行緒,第一個執行緒每隔200毫秒列出一偶數;第二個執行緒每隔300毫秒列出一3的倍數,直到二個執行緒列出所有小於100的數 程式Q12_1.java
範例3:跑馬燈Applet+Thread(1/2) public void start() { thread = new Thread(this); thread.start(); } public void run() { while(true) { scrollStatus(); try { Thread.sleep(50); } catch(InterruptedException e) { break; } public void scrollStatus() { int length = msg.length(); int index = pos; for( int i = 0; i< width; i++) if (index >= length) statusBuf.setCharAt(i, ' '); else statusBuf.setCharAt(i, msg.charAt(index)); index++; getAppletContext().showStatus(statusBuf.toString()); pos++; if(pos >= length) pos = 0; Ch05_03.java import javax.swing.*; import java.util.*; public class Ch05_03 extends JApplet implements Runnable { Thread thread; String msg; StringBuffer statusBuf; int pos; int width; public void init() msg = getParameter("message"); width = Integer.valueOf(getParameter("width")).intValue(); pos = 0; statusBuf = new StringBuffer(width); statusBuf.setLength(width); int length = msg.length(); char buffer [] = new char[width]; for(int i = 0; i < width; i++) buffer[i] = ' '; msg.getChars(0, length, buffer, (width-length) / 2); msg = new String(buffer); }
範例3:跑馬燈Applet+Thread(2/2) Ch05_03.htm <html> <head> <title> </title> </head> <body> <applet code="Ch05_03.class" width=0 height=0> <param name="message" value="歡迎光臨我的網站!"> <param name="width" value="100"> </applet> </body> </html>
範例4:在JFrame上顯示動畫(1/3) /* 程式範例 */ import javax.swing.*; import java.awt.*; import java.awt.event.*; // 繼承JFrame類別, 實作ActionListener介面 public class Ch05_05 extends JFrame implements ActionListener { private int offset = -10; private Timer timer; private AnimationPane animationPane; // 建構子 public Ch05_05() { super("動畫功能的顯示範例"); int delay = 100; timer = new Timer(delay, this); timer.setInitialDelay(0); Container c = getContentPane(); c.setLayout(new FlowLayout()); c.setBackground(Color.gray); Toolkit toolkit = Toolkit.getDefaultToolkit(); Image image = toolkit.getImage("bleye.gif"); animationPane = new AnimationPane(image); c.add(animationPane); timer.start(); }
範例4:在JFrame上顯示動畫(2/3) // 顯示動畫的JPanel class AnimationPane extends JPanel { Image image; // 建構子 public AnimationPane(Image image) { setPreferredSize(new Dimension(250, 100)); setBackground(Color.lightGray); this.image = image; } public void paintComponent(Graphics g) { super.paintComponent(g); int width = getWidth(); int height = getHeight(); // 計算圖片的尺寸 int imgWidth = image.getWidth(this); int imgHeight = image.getHeight(this); g.drawImage(image,((offset*5)%(imgWidth+width)) - imgWidth, (height-imgHeight)/2, this);
範例4:在JFrame上顯示動畫(3/3) // 實作事件處理方法 public void actionPerformed(ActionEvent evt) { offset++; animationPane.repaint(); // 重繪 } // 主程式 public static void main(String[] args) { // 建立Swing應用程式 Ch05_05 app = new Ch05_05(); // 關閉視窗事件, 結束程式的執行 app.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } }); app.setSize(300, 150); // 設定尺寸 app.setVisible(true); // 顯示視窗
期末考 作一個賽馬遊戲,玩者可以選擇一次要押多少籌碼,選一匹馬,如果獲勝,可以得到更多籌碼 每次比賽有五匹馬