什麼是多線程編程
多線程編程技術是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的線程操作很簡單,但實事並非如此。在實際工作中,可能需要綜合考慮很多問題,比如設置監控線程、暫停、命名和協調線程、設置線程的優先級、共享變量、線程同步、線程池、線程組等。對於這些線程相關的操作,筆者不在本章中詳細講述了。如果讀者有興趣,可參考相應的專著,畢竟這部分不是一兩篇文章可以完全講解完的。