什麼是多線程編程
多線程編程技術是Java語言的重要特點。多線程編程的含義是將程式任務分成幾個並行的子任務。特別是在網路編程中,你會發現很多功能是可以並發執行的。比如網路傳輸速度較慢、使用者輸入速度較慢,你可以用兩個獨立的線程去完成這兩個功能,而不影響正常的顯示或其它功能。
多線程是與單線程比較而言的,普通的Windows採用單線程程式結構,其工作原理是:主程式有一個訊息迴圈,不斷從訊息佇列中讀入訊息來決定下一步所要乾的事情,一般是針對一個函數,只有等這個函數執行完之後,主程式才能接收另外的訊息來執行。比如子函數功能是在讀一個網路資料,或讀一個檔案,只有等讀完這個資料或檔案才能接收下一個訊息。在執行這個子函數過程中你什麼也不能幹。但往往讀網路資料和等待使用者輸入有很多時間處於等待狀態,多線程利用這個特點將任務分成多個並發任務後,就可以解決這個問題。
Java中的線程類
要學會Java中的多線程編程,就得知道如何?支援多線程的類。Java中實現多線程的類有兩種方法:
1.擴充java.lang.Thread類,用它覆蓋Thread類的run方法。
2.產生實現java.lang.Runnable介面的類並將其它的執行個體與java.lang.Thread執行個體相關聯。
Thread類是負責向其它類提供線程支援的最主要的類,要使用一個類具有線程功能,在Java中只要簡單地從Thread類派生一個子類就可以了擴充Thread類,如printThread.java。
下面我們將通過執行個體來介紹如何使用這兩種方法編寫自己的多線程應用程式。
建立java.lang.Thread類的子類
Thread類最重要的方法是run方法。run方法是新線程執行的方法,因此產生java.lang.Thread的子類時,必須有相應的run方法。
//PrintThread.java public class PrintThread extends Thread//繼承Tread類 private int count=0 //定義一個count變數用於統計列印的次數並共用變數 public static void mainString args//main方法開始 PrintThread p=new PrintThread//建立一個線程執行個體 p.start//執行線程 for{;;}//主線程main方法執行一個迴圈,for執行一個死迴圈
count++ System.out.printcount+″:Main/n″//主線程中列印count +“main”變數的值,並換行
public void run//線程類必須有的run()方法 for{;;}
count++ System.out.printcount+″:Thread/n″ |
上面這段程式便是繼承java.lang.Tread並覆蓋run的方法。用JAVA 虛擬機器啟動程式時,這個程式會先產生一個線程並調用程式主類的main方法。這個程式中的main方法產生新線程,串連列印“Thread”。在啟動線程之後,主線程繼續列印“Main”。
編譯並執行這個程式,然後立即按“Ctrl+C”鍵中斷程式,你會看到上面所述的兩個線程不斷列印出:XXX:main…..XXX:Thread…. XXX代表的是數字,也就是上面count的值。在筆者的機器上,不同時刻這兩個線程列印的次數不一樣,先列印20個main(也就是先執行20次主線程)再列印出50次Thread,然後再列印main……
提示:為了便於查看該程式的執行結果,你可以將執行結果匯入一個文字檔,然後開啟這個檔案查看各線程執行的情況。如運行:
javac PrintThread.java
Java PrintThread>1.txt
第一個命令javac PrintThread.java是編譯java程式,第二個是執行該程式並將結果匯入1.txt檔案。這樣,開啟這個檔案,你就可以看見詳細的結果了(注意:程式的執行時間不能太長,不然產生的1.txt檔案會很龐大)。當然你可以直接執行命令:java PrintThread。
實現java.lang.Runnable介面
運行線程的另一種方法是實現Runnable介面,然後產生運行這個類的線程即可。Runnable介面是定義在java.lang包中的一個介面,其中只提供了一個抽象的run聲明。
下面我們來看看如何?Runnable介面,而不是擴充Thread類。
//PrintRunnableThread.java 實現Runnable介面 public class PrintRunnableThread implements Runnable public static void mainString args Thread t=new Threadnew PrintRun nableThread //t.setPriorityThread.MAX_PRIORI TY//設定最大優先順序 t.start//線程開始 for{;;}//不停地列印字元M,代表主線程main System.out.println″M″
public void run for{;;}//不停地列印字元T,代表線程thread System.out.println″T″ |
運行本程式,你會發現執行結果和PrintThread.java的執行結果很類似,甚至你可以再添加一個變數,並列印出相似的線程執行結果。
提示:與前例不同的是,如果去掉t.setPriorityThread.MAX_PRIORITY語句前的注釋符,就將線程設定成最大優先順序,執行結果就大不一樣了。
線程組ThreadGroup
java.lang.ThreadGroup類表示一組線程(可能包含其它ThreadGroup),用來實現按照特定功能對線程進行集中式分組管理。使用者建立的每個線程均屬於某線程組,這個線程組可以線上程建立時指定,也可以不指定線程組以使該線程處於預設的線程組之中。但是,一旦線程加入某線程組,該線程就一直存在於該線程組中直至線程終止,不能在中途改變線程所屬的線程組。
下面的代碼示範了如何操作和使用ThreadGroup。
//ThreadGroupTest.java public class ThreadGroupTest implements Runnabl e public void run
public static void main(String args[]) //產生一個新的線程組,並將兩個線程對象放到該線程組裡。 ThreadGroup threadgroup=new ThreadGroup″線程組″ Thread t1=new Threadthreadgroupnew ThreadGrou pTest″線程 1″ Thread t2=new Threadthreadgroupnew ThreadGrou pTest″線程 2″ //找到頂級的父線程 ThreadGroup parent=Thread.currentThread.getThrea dGroup//得到當前線程的線程組 whileparent.getParent=null
parent=parent.getParent//得到父線程
//list方法列印出當前線程組的所有內容線程和子線程組 parent.list |
提示:上段程式中,list()方法顯示ThreadGroup樹(可能包括線程和線程組,所以構成了線程樹)的結構和內容。運行上面程式你就會對線程組有了一定的瞭解。
線程優先順序
雖然我們說線程是並發啟動並執行。然而事實常常並非如此。當系統中只有一個CPU時,以某種順序在單CPU情況下執行多線程被稱為調度scheduling。Java採用的是一種簡單、固定的調度法,即固定優先順序調度。這種演算法是根據處於可運行線程的相對優先順序來實行的。當線程產生時,它繼承原線程的優先順序。在需要時可對優先順序進行修改。在任何時刻,如果有多條線程等待運行 系統選擇優先順序最高的可運行線程運行。只有當它停止、自動放棄、或由於某種原因成為非運行狀態優先順序的線程時才能運行。如果兩個線程具有相同的優先順序它們將被交替地運行。
Java中的第一個線程都有優先順序,線程的優先順序是介於Thead.MIN_PRIORITY到Thread.MAX_PRIORITY之間的整數介於0到10之間。預設情況下,線程的優先順序是5即NORM_PRIORITY。我們可以用形如Thread.setPriorityThread.MIN_PRIORITY這樣的運算式來設定線程的優先順序稍後會在常式中用到。也可以通過getPriority來得到線程的優先順序,還可以通過setPriority線上程建立之後的任意時間改變線程的優先順序。
提示:當線程中的代碼建立一個新線程對象時,這個新線程擁有與建立它的線程一樣的優先順序。
線程的管理
單線程的程式都有一個main執行體,它運行一些代碼,當程式結束執行後,程式結束運行。在Java中我們要得到相同的應答,必須稍微進行改動。只有當所有的線程退出後,程式才能結束。只要有一個線程一直在運行,程式就無法退出。
線程包括new()開始、running()運行、wait()等候和done結束執行狀態。第一次建立線程時,都位於new狀態,在這個狀態下,不能運行線程,只能等待。這時,線程要麼調用start方法開始運行,要麼送往done狀態結束。位於done中的線程已經結束執行,這是線程的最後一個狀態。一旦線程位於這個狀態,就不能再次出現,而且當Java虛擬機器中的所有線程都位於done狀態時,程式就強行中止。
當前正在執行的所有線程都位於running狀態,在程式之間用某種方法把處理器的執行時間分成時間片。位於running狀態的每個線程都是能啟動並執行,但在一個給定的時間內,每個系統處理器只能運行一個線程。與位於running狀態的線程不同,由於某種原因,可以把已經位於waiting狀態的線程從一組可執行線程中刪除。如果線程的執行被中斷,就回到waiting狀態。這時,線程可能被掛起,在系統資源上等候,或者被告知進入休眠狀態Sleep。該狀態的線程可以返回到running狀態,也能由stop送入done狀態。Thread類提供了Sleep()、Stop、Yield()、Suspend()和Resume()等方法來管理線程。
線程操作的其它概念
通過這期兩個程式的學習,可能你會認為Java的線程操作很簡單,但實事並非如此。在實際工作中,可能需要綜合考慮很多問題,比如設定監控線程、暫停、命名和協調線程、設定線程的優先順序、共用變數、線程同步、線程池、線程組等。對於這些線程相關的操作,筆者不在本章中詳細講述了。如果讀者有興趣,可參考相應的專著,畢竟這部分不是一兩篇文章可以完全講解完的。