讓你的程式具有「多工」 及「平行處理」的能力 執行緒 Thread 讓你的程式具有「多工」 及「平行處理」的能力
本章重點 什麼是「執行緒」 執行緒的程式架構 執行緒的生命週期 執行緒的同步協調 執行緒優先權的管理 「執行緒群組」的管理 Java語言實務
什麼是「執行緒」 支援「平行處理」的執行控制機制,它可以執行程式中任何一組相關且可與程式中其它部分「平行處理」的程式片斷。 播放背景音樂 執行緒a: 數數運算 執行緒c: 動畫顯示 執行緒b: 程式(多執行緒) Java語言實務 執行緒
「執行緒」具有以下幾個基本特徵: 不是完整的程式。 隸屬於同一個程式的「執行緒」必需共享系統分配給所屬主程式的資源(如記憶體空間、CPU時間等)。 每一個「執行緒」都有自己的「執行堆疊」及「程式計數器」 程式可以設定「執行緒」的執行優先順序 及隸屬的「執行緒群組」。 每個「執行緒」於程式執行時的可能狀態有:born、ready、running、blocked、suspend、sleeping、waiting及dead。 Java語言實務 執行緒
各作業系統對於具有相同優先權的「執行緒」有不同的處理方式 。 Java Runtime(Java虛擬機器)會於開始執行程式前,自動為程式建立一個「執行緒」以便控制及記錄程式執行時的狀態,這個「執行緒」通稱為main thread,當main thread結束時(也就是說進入dead狀態時),程式的執行也就結束。 Java語言實務 執行緒
執行緒的程式架構 在Java程式中,「執行緒」是以「執行緒」物件來表示,亦即在程式中一個「執行緒」物件就代表了一個「執行緒」 二種指定程式中部分的程式碼給「執行緒」物件 以便執行的方式 在程式中建立Thread類別的子類別,然後直接在子類別中改寫繼承自Thread類別的run() 方法 在程式中定義界面Runnable,然後改寫Runnable的run() 方法 Java語言實務 執行緒
程式 Ex11_1 以Thread的子類別建立執行緒(1/2) import java.io.*; import java.lang.Math.*; /* 建立Thread的子類別 */ class Counter_Thread extends Thread { private static int threadNum=0; /* 類別變數threadNum用來累計執行緒個數 */ private int currentThread, loopLimit; public Counter_Thread(int loopLimit) /* 變數loopLimit為計數值的上限 */ { this.loopLimit = loopLimit; currentThread = threadNum++; } private void pause(double seconds) /* 讓執行緒進入睡眠狀態 */ { try {Thread.sleep(Math.round(1000.0*seconds));} catch(InterruptedException ie) {}; public void run() /* 執行緒的執行由此開始 */ { for(int i=0; i<loopLimit; i++) { System.out.println("Thread " + currentThread + ": counter=" + i); pause(Math.random()); } // end for } // end of Counter Java語言實務 執行緒
程式 Ex11_1 以Thread的子類別建立執行緒(2/2) /* 測試執行緒 */ public class Counter { public static void main(String[] args) { /* 建立執行緒物件 */ Counter_Thread counterThread_1 = new Counter_Thread(8); Counter_Thread counterThread_2 = new Counter_Thread(8); counterThread_1.start(); /* 啟動執行緒 */ counterThread_2.start(); } // end main } // end Counter Java語言實務 執行緒
程式 Ex11_1 可能的執行結果: Thread 0: counter=0 Thread 1: counter=0 Java語言實務 執行緒
程式 Ex11_2以定義Runnable界面建立執行緒 (1/2) import java.io.*; import java.lang.Math.*; /* 在類別中定義界面Runnable */ class Counter implements Runnable { private static int threadNum=0; /* 類別變數threadNum用來累計執行緒個數 */ private int currentThread, loopLimit; public Counter(int loopLimit) /* 變數loopLimit為計數值的上限 */ { this.loopLimit = loopLimit; currentThread = threadNum++; } private void pause(double seconds) /* 讓執行緒進入睡眠狀態 */ { try {Thread.sleep(Math.round(1000.0*seconds));} catch(InterruptedException ie) {}; public void run() /* 執行緒的執行由此開始 */ { for(int i=0; i<loopLimit; i++) { System.out.println("Thread " + currentThread + ": counter=" + i); pause(Math.random()); } // end for } // end run } // end Counter Java語言實務 執行緒
程式 Ex11_2以定義Runnable界面建立執行緒 (2/2) /* 測試執行緒 */ public class Counter2 { public static void main(String[] args) { /* 建立執行緒物件 */ Thread counterThread_1 = new Thread(new Counter(8)); Thread counterThread_2 = new Thread(new Counter(8)); /* 啟動執行緒 */ counterThread_1.start(); counterThread_2.start(); } // end main } // end Counter2 Java語言實務 執行緒
執行緒的生命週期 取得執行權 讓出執行權 born start running dead stop wait suspend sleep Issue I/O request ready blocked waiting sleeping suspending Sleep interval expires I/O completion resume notify or notifyAll Java語言實務 執行緒
程式 Ex11_3 使用isAlive()及join()範例(1/2) import java.io.*; import java.lang.Math.*; class Counter implements Runnable { private int loopLimit; private String threadName; // name of thread Thread t; /* 變數t儲存執行緒物件的參考位址(reference) */ public Counter(int loopLimit, String threadName) { this.loopLimit = loopLimit; this.threadName = threadName; /* 建立執行緒名稱為變數threadname的值 */ /* 保留字this指定該執行緒所要執行的程式片斷就在這個類別中定義 */ t = new Thread(this, threadName); t.start(); } private void pause(double seconds) { try {Thread.sleep(Math.round(1000.0*seconds));} catch(InterruptedException ie) {}; public void run() /* 執行緒的執行進入點 */ { for(int i=0; i<loopLimit; i++) /* 列印迴圈控制變數I的值 */ { System.out.println("Thread: " + threadName + ": counter=" + i); pause(Math.random()); } // end for } // end run } // end Conter Java語言實務 執行緒
程式 Ex11_3 使用isAlive()及join()範例(2/2) public class AliveJoin { public static void main(String[] args) { Counter c_1 = new Counter(2, "1"); Counter c_2 = new Counter(4, "2"); System.out.println("Thread 1 is alive: " + c_1.t.isAlive()); System.out.println("Thread 2 is alive: " + c_2.t.isAlive()); try { c_1.t.join(); /* 等待第一個執行緒結束 */ c_2.t.join(); /* 等待第二個執行緒結束 */ } catch (InterruptedException e) {} /* 此時二個執行緒皆已結束執行 */ } // end main } // end AliveJoin Java語言實務 執行緒
程式 Ex11_3 可能的執行結果: Thread 1 is alive: true Thread 2 is alive: true Thread: 1: counter=0 Thread: 2: counter=0 Thread: 1: counter=1 Thread: 2: counter=1 Thread: 2: counter=2 Thread: 2: counter=3 Thread 1 is alive: false Thread 2 is alive: false Java語言實務 執行緒
程式 Ex11.4 使用suspend()及resume()方法範例 (1/2) (修改Ex11_3的public class AliveJoin )以下述public class ResumeSuspend替代) public class ResumeSuspend { public static void main(String[] args) { Counter c_1 = new Counter(4, "1"); Counter c_2 = new Counter(4, "2"); c_2.t.suspend(); /* 暫停第二個執行緒的執行 */ System.out.println("Suspending Thread 2." ); /* 藉由本方法的執行可確保在第一個執行緒結束後才會resume第二個執行緒 */ try {c_1.t.join(); } catch (InterruptedException e) {} System.out.println("Thread 1 is alive: " + c_1.t.isAlive()); System.out.println("Thread 2 is alive: " + c_2.t.isAlive()); c_2.t.resume(); /* 恢復第二個執行緒的執行 */ System.out.println("Resuming Thread 2." ); } // end main } // end ResumeSuspend Java語言實務 執行緒
執行之可能結果為: Thread: 1: counter=0 Thread: 2: counter=0 Suspending Thread 2. Thread: 1: counter=1 Thread: 1: counter=2 Thread: 1: counter=3 Thread 1 is alive: false Thread 2 is alive: true Resuming Thread 2. Thread: 2: counter=1 Thread: 2: counter=2 Thread: 2: counter=3 Java語言實務 執行緒
執行緒的同步協調 當二個或二個以上的執行緒要使用共享的資源(或資料)時,這些執行緒就必須彼此協調存取共享資源的順序,否則就可能會有意想不到的結果出現。 synchronized 保留字 wait()及notify()方法 Java語言實務 執行緒
程式 Ex11_5使用synchronized範例 (1/3) import java.io.*; class NumberProducer /* 模擬號碼產生機 */ { int number=0; /* 記錄目前可用號碼的變數 */ public NumberProducer(int initialValue) { number = initialValue; /* 將目前可用的號碼設定成initialValue的值 */ } public int getNumber() /* 取得號碼並更新目前可用的號碼 */ { int num; num = number; /* 休眠1秒鐘讓其它的執行緒在完成取得號碼動作完成前獲得執行權*/ try { Thread.sleep(1000); } catch (InterruptedException e) {}; number++; /* 更新目前可用之號碼 */ return(num); /* 回傳取得之號碼 */ } } // end NumberProducer Java語言實務 執行緒
程式 Ex11_5使用synchronized範例 (2/3) class Visitor extends Thread /* 模擬取號客戶 */ { int number; NumberProducer np; /* 變數np存放客戶所使用的號碼機 */ public Visitor(NumberProducer np) { this. np= np; } public void run() { number = np.getNumber(); /* 領取號碼 */ } // end Visitor Java語言實務 執行緒
程式 Ex11_5使用synchronized範例 (3/3) public class NumMachine { public static void main(String[] args) { NumberProducer np; Visitor v0, v1, v2, v3; np = new NumberProducer(1); /* 產生號碼機物件 */ v0 = new Visitor(np); /* 建立執行緒模擬四位先後到達的客戶及取號 */ v0.start(); v1 = new Visitor(np); v1.start(); v2 = new Visitor(np); v2.start(); v3 = new Visitor(np); v3.start(); try { v0.join(); v1.join(); v2.join(); v3.join(); /* 等待上述四個執行緒執行結束 */ } catch (InterruptedException e) {}; /* 列印取得的號碼 */ System.out.println("Visitor 0: Number: " + v0.number); System.out.println("Visitor 1: Number: " + v1.number); System.out.println("Visitor 2: Number: " + v2.number); System.out.println("Visitor 3: Number: " + v3.number); } // end main } // end NumMachine Java語言實務 執行緒
程式 Ex11_5執行後的結果為: Visitor 0: Number: 1 Visitor 1: Number: 1 Visitor 2: Number: 1 Visitor 3: Number: 1 由於各「執行緒」在還沒有完成共享資源的存取前就被其它的「執行緒」取得執行權,結果原號碼在還沒有累加更新前,就被另一個「執行緒」取得而使用,因此每個客戶取得的號碼皆為1。 Java語言實務 執行緒
解決上述取號不正確的現象,我們必須在getNumber()方法之前加上synchronized保留字,修改如下: public synchronized int getNumber() {... } Java Runtime會確保在任何一個時間點,只有一個「執行緒」可以進入含有syhchronized保留字宣告的方法。 修改後,程式執行的結果: Visitor 0: Number: 1 Visitor 1: Number: 2 Visitor 2: Number: 3 Visitor 3: Number: 4 Java語言實務 執行緒
程式 Ex11_6 wait()及notify()使用範例 (1/5) import java.io.*; class NumberQueue /* 模擬號碼產生器中存放及更新號碼的類別 */ { int number=0; /* 存放所產生的號碼 */ boolean tickenTaken=true; /* 記錄號號否已被取走 */ public NumberQueue(int initialValue) number = initialValue; /* 設定號碼初值為initialValue變數的值 */ } public synchronized int getNumber() if (tickenTaken) /* 如果號碼已被取走則等待新號碼產生後後才繼續執行 */ try { wait(); }catch(InterruptedException e){}; tickenTaken=true; /* 設定號碼已被取走 */ notify(); /* 告知佇列中第一個執行緒可以進入ready狀態以便恢復執行 */ return(number); /* 傳回取得的號碼 */ Java語言實務 執行緒
程式 Ex11_6 wait()及notify()使用範例 (2/5) public synchronized void setNumber() /* 設定ticketNumber的值 */ { if (!tickenTaken) /* 如果號碼尚末被取走則等待號碼被取走後才繼續執行 */ try {wait();} catch(InterruptedException e){}; number++; /* 設定新的號碼 */ tickenTaken=false; /* 設定號碼尚未被取走 */ notify(); /* 告知等待佇列中的第一個執行緒可以進入ready狀態以便恢復執行 */ } } // end of NumberQueue Java語言實務 執行緒
程式 Ex11_6 wait()及notify()使用範例 (3/5) class Consumer extends Thread /* 取得號碼的執行緒 */ { int ticketNumber; NumberQueue nq; public Consumer(NumberQueue nq) { this.nq = nq; } public void run() { ticketNumber = nq.getNumber(); /* 取得一個號碼 */ } } // end of Consumer class Producer extends Thread /* 設定新號碼的執行緒 */ { NumberQueue nq; public Producer(NumberQueue nq) { this. nq = nq; } while (true) nq.setNumber(); /* 設定新的號碼 */ } // end of Producer Java語言實務 執行緒
程式 Ex11_6 wait()及notify()使用範例 (4/5) public class NumMachine2 { public static void main(String[] args) { NumberQueue nq; /* 變數nq用來存放號碼產生器的參考 */ Consumer c0, c1, c2, c3; Producer p0; nq = new NumberQueue(0); /* 建立號碼產生器 */ p0 = new Producer(nq); /* 建立設定新號碼的執行緒 */ p0.start(); c0 = new Consumer(nq); /* 建立第一個取得號碼的執行緒 */ c0.start(); try {c0.join();} catch (InterruptedException e) {}; /* 等待第一個執行緒執行結束 */ c1 = new Consumer(nq); /* 建立第二個取得號碼的執行緒 */ c1.start(); try { c1.join();} catch (InterruptedException e) {}; /* 等待第二個執行緒執行結束 */ c2 = new Consumer(nq); c2.start(); try {c2.join();} catch (InterruptedException e) {}; c3 = new Consumer(nq); c3.start(); try {c3.join();} catch (InterruptedException e) {}; Java語言實務 執行緒
執行結果: 程式 Ex11_6 wait()及notify()使用範例 (5/5) Consumer 0: Ticket Number: 1 /* 列印各執行緒取得的號碼 */ System.out.println("Consumer 0: Ticket Number: " + c0.ticketNumber); System.out.println("Consumer 1: Ticket Number: " + c1.ticketNumber); System.out.println("Consumer 2: Ticket Number: " + c2.ticketNumber); System.out.println("Consumer 3: Ticket Number: " + c3.ticketNumber); System.out.println("Press ctrl+c to stop!"); } // end of main } // end of NumMachine2 執行結果: Consumer 0: Ticket Number: 1 Consumer 1: Ticket Number: 2 Consumer 2: Ticket Number: 3 Consumer 3: Ticket Number: 4 Press ctrl+c to stop! Java語言實務 執行緒
執行緒優先權的管理 Java以優先權來決定「執行緒」的執行順序。 優先權是一個界於1(MIN_PRIORITY)至10(MAX_PRIORITY)的數值,預設值是5(NORM_PRIORITY)。 數值愈大,代表優先權愈高。高優先權的「執行緒」可以中斷低優先權的「執行緒」,以便取得執行權。 Java語言實務 執行緒
程式 Ex11.7 設定執行緒優先權範例(1/3) import java.io.*; class Accumulator extends Thread /* 宣告執行緒類別 */ { int sum; /* 記錄累加值的變數 */ boolean running = true; /* 控制迴圈執行次數的變數 */ Accumulator (int sum) /* 建構子-設定初值 */ { this.sum = sum; } public void run() /* 執行緒的執行進入點 */ while(running) /* 累加數值 */ sum = sum + 1; Java語言實務 執行緒
程式 Ex11.7 設定執行緒優先權範例(2/3) public class Priority { public static void main(String[] args) Accumulator a1, a2; /* 將主執行緒的優先權設為最高 */ Thread.currentThread().setPriority(Thread.MAX_PRIORITY); a1 = new Accumulator(0); /* 建立計算累加值的執行緒 */ a2 = new Accumulator(0); a1.setPriority(Thread.NORM_PRIORITY-3); /* 設定執行緒的優先權值 */ a2.setPriority(Thread.NORM_PRIORITY+3); a1.setName("Accumulator_a1"); /* 設定執行緒的名稱 */ a2.setName("Accumulator_a2"); a1.start(); /* 啟動執行緒 */ a2.start(); try {Thread.sleep(1000);} /* 主執行緒進入休眠狀態 */ catch(InterruptedException e){} Java語言實務 執行緒
程式 Ex11.7 設定執行緒優先權範例(3/3) a1.running = false; /* 設定running值以便結束執行緒的累加運算 */ a2.running = false; try { a1.join(); /* 等待執行緒結束 */ a2.join(); } catch (InterruptedException e) {}; /* 列印累加結果及相關資訊 */ System.out.println("[Main Thread name: " + Thread.currentThread().getName() + " Priority: " + Thread.currentThread().getPriority() + "]"); System.out.println("Accumulator a1: sum = " + a1.sum + " [Thread Name: " + a1.getName() + " Priority: " + a1.getPriority() + "]"); System.out.println("Accumulator a2: sum = " + a2.sum + " [Thread Name: " + a2.getName() + " Priority: " + a2.getPriority()+"]"); } // end of main } // end of Priority Java語言實務 執行緒
執行可能結果: [Main Thread name: main Priority: 10] Accumulator a1: sum = 0 [Thread Name: Accumulator_a1 Priority: 2] Accumulator a2: sum = 96801653 [Thread Name: Accumulator_a2 Priority: 8] 由於「執行緒」Accumulator_a1的優先權值為2,這使得一直到程式結束執行時,Accumulator_a1可能仍無法取得執行權,因此Accumulator_a1的累加值sum為0。 Java語言實務 執行緒
「執行緒群組」的管理 執行緒 可依應用上的需求將執行緒分組,方便管理。 透過執行緒群組所提供的方法同時啟動、停止、暫停群組中的所有執行緒,或同時設定該群組之執行緒的最大優先權。 執行緒群組內也可以包含另一個執行緒群組。 Java語言實務 執行緒
程式 Ex11_8 執行緒群組使用範例(1/3) import java.io.*; class Accumulator extends Thread /* 宣告執行緒類別 */ { int sum; /* 記錄累加值的變數 */ boolean running = true; Accumulator (ThreadGroup group, String threadName, int initValue) /* 建構子 */ { /* 呼叫上層建構子,建立隸屬於群組gorup名稱為threadName的執行緒 */ super(group, threadName); this.sum = initValue; /* 設定累加變數的初值 */ } public void run() /* 執行緒的執行進入點 */ while(running) sum = sum + 1; } // end of Accumulator Java語言實務 執行緒
程式 Ex11_8 執行緒群組使用範例(2/3) public class TGroup { public static void main(String[] args) Accumulator a1, a2; ThreadGroup group; group = new ThreadGroup("Accumulator_Group"); /* 建立執行緒群組 */ a1 = new Accumulator(group, "Accumulator_a1", 0); /* 建立計算累加值的執行緒 */ a2 = new Accumulator(group, "Accumulator_a2", 0); /* 將主執行緒的優先權設為最高 */ Thread.currentThread().setPriority(Thread.MAX_PRIORITY); a1.setPriority(Thread.NORM_PRIORITY-3); /* 設定執行緒的優先權值 */ a2.setPriority(Thread.NORM_PRIORITY+3); a1.start(); /* 啟動執行緒(亦可透過執行緒群組的start()方法同時啟動a1及a2)*/ a2.start(); Java語言實務 執行緒
程式 Ex11_8 執行緒群組使用範例(3/3) try {Thread.sleep(1000);} /* 主執行緒進入休眠狀態 */ catch(InterruptedException e){} group.stop(); /* 結束執行緒群組內所有執行緒的執行 */ try { a1.join(); /* 等待執緒行結束 */ a2.join(); } catch (InterruptedException e) {}; /* 列印執行緒群組相關資訊 */ System.out.println("[Thread Group Name: "+group.getName()+ ", Max Priority: "+group.getMaxPriority()+"]"); /* 列印累加結果 */ System.out.println("Accumulator a1: sum = "+a1.sum); System.out.println("Accumulator a2: sum = "+a2.sum ); } // end of main } // end of TGroup Java語言實務 執行緒
執行可能結果: [Thread Group Name: Accumulator_Group, Max Priority: 10] Accumulator a1: sum = 0 Accumulator a2: sum = 160567022 Java語言實務 執行緒