精彩Java 2程式設計 <吳逸賢、吳目誠 編著> B4 多執行緒 精彩Java 2程式設計 <吳逸賢、吳目誠 編著>
目錄 B4-1 建立多執行緒的程式 B4-2 執行緒的運作 B4-3 執行緒的排程與同步 B4-4 練習與實作
B4-1 建立多執行緒的程式 認識執行緒 Java的main執行緒 以Thread建立多執行緒 以Runnable介面建立多執行緒 顯示與設定執行緒資訊
認識執行緒 : 所謂執行緒(thread)是指單一而連續的程式執行流程。 單一執行緒在同一時間只能執行一個指令,當執行緒執行完成某一區塊的程式碼後,才能循序(sequential)進入下一段程式碼。 多執行緒(multi-thread)是指同時執行多個程式執行流程,而每一個執行流程都能分別獨立運作。
單一執行緒的圖示如下:
多執行緒程式運作方式如下:
多執行緒與多工間的關係: 多執行緒是多工(multitasking)觀念的延伸,多工是我們較為熟悉的程式特性。 多執行緒與多工(multitasking)的不同點在於多工是同一時間執行多個程式,而多執行緒是指同一時間執行同一個程式中的不同程式區塊。
Java的main執行緒: 每一個程式開始執行時,至少有一個執行緒。 取得目前正在執行的執行緒是使用currentThread()方法,而取得執行緒的名稱是使用getName()方法,讓執行緒暫停一段時間的方法為sleep()。
下面就是顯示main執行緒名稱和控制執行緒暫停的程式:
程式編譯與執行結果如下:
程式碼說明: 按下 + 可以中斷程式執行。 這個執行緒會不斷執行while程式區塊,每次執行到Thread.sleep(1000)方法時,執行緒將暫停一秒鐘,然後繼續執行,使得每間隔一秒鐘(sleep中參數值為千分之一秒),在視窗上會顯示一個新的整數值。
以Thread建立多執行緒: 除了Java預設的執行緒外,我們可以繼承Thread類別來建立執行緒物件。 Java預設的執行緒會從main()方法開始執行,而Thread類別建立的執行緒物件則從run()方法開始執行。
以下就是繼承Thread類別的程式架構: class newthread extends Thread { //建構函數 public newthread(){ …… } //啟動執行緒方法 public void run(){
利用上述的newthread類別,就可以在main()方法中,建立新的執行緒物件,程式碼舉例如下: newthread t1=new newthread();
建立執行緒物件後,使用start()方法就可以讓執行緒物件開始運作,執行run()方法,其命令格式如下: t1.start();
以下就是以Thread建立多執行緒的程式碼:
程式編譯與執行結果如下:
以Runnable介面建立多執行緒: 想要建立多執行緒的類別已經繼承其他類別(例如:繼承Japplet、JFrame)時,便無法再繼承Thread類別,而必須透過Runable介面來實作多執行緒。
下面就是實作Runnable介面的程式架構: class classthread implements Runnable { //建構函數 public newthread(){ …… } //啟動執行緒方法 public void run(){
利用上述的classthread類別,就可以在main()方法中,建立新的執行緒物件,程式碼舉例如下: classthread r1=new classthread(); Thread t1=new Thread(r1); //建立執行緒物件
使用start()方法就可以讓執行緒物件開始運作,執行run()方法,其命令格式如下: t1.start();
以下就是以Runnable介面實作多執行緒的程式碼:
程式編譯與執行結果如下:
顯示與設定執行緒資訊: 為了讓您進一步了解Thread類別提供的方法,將相關方法與功能整理如下:
程式碼舉例如下:
程式編譯與執行結果如下:
B4-2 執行緒的運作 執行緒的存活與中斷 設定執行緒的優先權
執行緒的存活與中斷: 使用isAlive()方法,可以檢查執行緒物件是否還在執行,而使用interrupt()方法,可以中斷執行緒物件的執行。
下面就是檢查執行緒是否存活與中斷的程式碼:
程式編譯與執行結果如下:
設定執行緒的優先權: 每一個執行緒都會設定一個優先權值,優先權的權值範圍為1~10,1為最低優先權,而10是最高優先權。 優先權值高的執行緒將佔用較多的CPU資源,而優先權值低的執行緒必須等待其他較高權值的執行緒執行後,才會輪到使用CPU資源。 使用getPriority()方法可以取得執行緒的優先權值,而使用setPriority()方法可以設定執行緒的優先權值。
Thread類別中宣告有下面三個優先權常數:
以下就是用來實驗優先權狀態的程式碼:
程式編譯與執行結果如下:
B4-3 執行緒的排程與同步 執行緒的排程 執行緒方法的同步化 執行緒間的溝通
執行緒的排程: 若想在程式建立三個執行緒物件,希望第一個執行緒物件執行完成後,再執行第二個執行緒物件,最後才執行第三個執行緒物件,則可以使用join()方法來達成這個要求。
以下就是設定執行緒執行排程的程式碼:
程式編譯與執行結果如下:
執行緒方法的同步化: 使用同一個類別產生的多個執行緒物件,可以分別執行物件中的方法。 當類別中的方法設定為synchronized時,則同一時間只能有一個執行緒物件執行這個方法,其他想要執行這個方法的執行緒,必須等到此方法空閒時,才能開始執行。
以下就是使用synchronized的簡易程式舉例:
程式編譯與執行結果如下:
程式碼說明: 在方法中加入synchronized命令後,必須等待t1執行緒執行show_number方法完成後,接著才輪到t2執行緒執行show_number方法,最後輪到t3執行緒執行show_number方法。 同步功能主要是用來解決同時存取相同資源的問題,最簡單的例子是多個執行緒同時存取一個共同變數(將類別變數設定為靜態變數)。
沒有應用同步功能時,可能產生的錯誤的程式舉例如下:
程式編譯與執行結果如下:
程式碼說明: 存入1000元15次,結果存款總額卻只有5000元。 只要在save_money()方法前面加上synchronized命令,就可以修正錯誤,使其顯示正確的結果。
在save_money()方法前面加上synchronized命令後的執行結果如下:
修正後的程式碼說明: 將save_money()方法設定同步後,必須等前一個執行緒呼叫save_money()方法執行完成後,其他執行緒才能繼續呼叫save_money()方法。
執行緒間的溝通: 利用wait()方法和notify()方法,可以建立執行緒之間的溝通機制。 wait()方法可以讓執行緒進入等待狀態,而notify()方法可以喚醒第一個進入等待狀態的執行緒。另外,若要喚醒所有等待狀態的執行緒,可以使用notify()方法。 想要設計具有溝通機制的執行緒時,通常會將執行緒分為讀取和寫入兩個部分。
號碼球處理程式的程式結構如下:
號碼球處理程式說明: 號碼球輸入機執行緒會不斷產生一個數字,將數字放入號碼球處理機中,號碼球處理機只能裝7個號碼球,裝滿後就不能再裝,必須等待輸出後,才能再輸入號碼球。 號碼球輸出機則不斷從號碼球處理機中取出號碼球,當號碼球處理機中的號碼球被取空後,必須等待新輸入的號碼球。
程式碼如下:
程式編譯與執行結果如下:
B4-4 練習與實作 倍數的執行緒之一(執行緒的設定) 倍數的執行緒之二(執行緒的排程) 計算電話卡的總額 點歌機的輸入與輸出
倍數的執行緒之一(執行緒的設定): 請利用Thread類別所提供的方式和物件,如下圖列出7和9的倍數,並顯示執行緒的名稱和執行緒數。(設定每次顯示間隔一秒鐘)
問題解析與程式設計: 首先建立兩個新的執行緒7和9的倍數,在啟動執行緒後,指定執行緒的名稱,並顯示執行緒的名稱和執行緒數。 利用Thread建立執行緒multiple類別,在類別內建立multiple的建構函數和變數。 在類別內建立啟動執行緒的方法run(),將倍數相乘並顯示結果,分別建立try和catch例外處理機制,try區內設定執行緒停止一秒鐘,catch區內設定中斷的例外類別。
指定執行緒的名稱,並顯示執行緒的名稱和執行緒數的程式碼如下:
建立執行緒multiple類別,在類別內建立multiple的建構函數和變數的程式碼如下: //以Thread建立執行緒multiple類別 class multiple extends Thread { private String name; private int lp,sun; //建構函數multiple public multiple(String str1,int l){ name=str1; lp=l; }
在類別內建立啟動執行緒的方法run()的程式碼如下: public void run(){ for(int i=1;i<100;i++){ sun=lp*i; System.out.println(i+" * "+name+"為 "+sun); System.out.flush(); try{ //執行緒停止一秒鐘 sleep(1000); } catch(InterruptedException e){return;}
完整程式碼:
完成圖:
倍數的執行緒之二(執行緒的排程): 分別依序顯示5的1到5的倍數、7的1到5的倍數和9的1到5的倍數。(利用join()方法,並設定每次顯示間隔一秒鐘)
問題解析與程式設計: 建立三個新的執行緒5、7、9的倍數。 開始啟動建立的執行緒t1、t2、t3,利用join()方法設定執行緒執行排程。 利用Thread建立執行緒的類別,在類別內建立建構函數、變數和啟動執行緒的方法run(),將倍數相乘的for迴圈設定至5,並顯示相乘結果。
建立三個新的執行緒5、7、9的倍數,程式碼如下: //建立三個執行緒 multiple t1=new multiple("5的倍數",5); multiple t2=new multiple("7的倍數",7); multiple t3=new multiple("9的倍數",9);
利用join()方法設定執行緒執行排程,程式碼如下:
完整程式碼:
完成圖:
計算電話卡的總額: 一張100元的電話卡,分別在A、B、C、D這四個電話亭各通話使用三次,每次通話以6元為計費單位,請顯示這張電話卡在電話亭每次通話的餘額(設定同步計算電話卡的總額)。
問題解析與程式設計: 建立四個執行緒的b1、b2、b3、b4物件後,開始啟動建立的執行緒。 利用Thread建立執行緒order類別,在類別內建立變數、建構函數、啟動執行緒方法run()和phone_money函數。
在類別內建立變數、建構函數、啟動執行緒方法run()和phone_money函數的說明如下: 宣告變數sum初值為100,是電話卡總額,程式碼如下: private static int sum=100; 啟動執行緒方法run()的程式碼如下: public void run(){ for(int i=1;i<=3;i++) phone_money(name,2); }
建立phone_money方法,並在方法中加上synchronized表示設定同步執行,程式碼如下:
完整程式碼:
完成圖:
點歌機的輸入與輸出: 利用wait()和notify()的方法,撰寫一個號碼點歌機的程式碼,點歌最多能輸入10首記憶儲存,點歌若超過30首後,會自動跳出(點歌歌曲設定由100000至999999的號碼)。
問題解析與程式設計: 利用Thread建立輸入、輸出的點歌號碼input_num、output_num類別的執行緒。 建立點歌號碼處理機musicbox類別,在類別內建立變數、陣列、建構函數,並建立同步的input_num和output_num函數,當輸入input_num點歌號碼的陣列超過9時,使用wait()方法等待喚醒,小於8時則使用notify()方法等待喚醒。 當輸入30首點歌號碼後,則結束執行的條件。
建立輸入、輸出的點歌號碼input_num、output_num類別的執行緒,程式碼如下:
在類別內建立變數、陣列、建構函數,並建立同步的input_num和output_num函數,程式碼如下:
結束執行的條件,程式碼如下: if (t1.count >= 30){ run_num = false; break; }
完整程式碼:
完成圖: