編寫線程程序主要是構造線程類。構造線程類的方式主要有兩種,一種是通過構造類java.lang.Thread的子類,另一種是通過構造方法實現接口java.lang.Runnable的類。因為類java.lang.Thread實際上也是實現了接口java.lang.Runnable的類,所以上面兩種構造線程類的方法從本質上都是構造實現接口java.lang.Runnable的類。下面將具體介紹著兩種方法。
類java.lang.Thread的每個實例對象就是Java程序的一個線程,但是一般只能通過構造其子類的實例對象來實現。構造類java.lang.Thread的子類的主要目的是為了讓線程類的實例對象能完成線程程序所需要的功能。
在編寫類java.lang.Thread的子類的過程中,一個很重要的步驟就是編寫run成員方法。這個成員方法實際上是對類java.lang.Thread的成員方法public void run()的覆蓋。它包含了線程所需要執行的代碼。雖然線程的執行代碼在成員方法run中,但是啟動或運行線程並不是直接調用成員方法run的,而是調用類java.lang.Thread的成員方法public void start()啟動線程。
如果直接調用成員方法run,則一般來說會立即執行成員方法run,從而失去線程的特性。在調用成員方法start之後,Java虛擬機會自動啟動線程,從而由Java虛擬機進一步統一調度線程,實現各個線程一起並發運行。Java虛擬機決定是否開始以及何時開始運行該線程,而線程的運行實際上就是執行線程的成員方法run。
Java語言的語法規定每個類只能有一個直接父類,所以通過接口java.lang.Runnable構造線程是在構造線程過程中可能出現的多重繼承問題的一種解決方案。
通過接口java.lang.Runnable構造線程首先需要編寫一個實現接口java.lang.Runnable的類。接口java.lang.Runnable只聲明了唯一的成員方法void run(),因此,編寫實現接口java.lang.Runnable的類的一般格式如下:
public class A extends B implements Runnable
{
//類體的其它部分
public void run()
{
成員方法run的方法體
}
//類體的其它部分
}
其中,A和B分別表示兩個類的名稱。在上面的格式中,“extends B”不是必須的,是否需要應當由具體需求而定。如果“extends B”是必須的,則通過接口java.lang.RUnnable構造線程的方法似乎是非常有必要的。因為類A已經有一個直接父類B,所以類A不能再是類java.lang.Thread的直接子類。借助於接口java.lang.Runnable可以避開這個問題。與通過構造類java.lang.Thread的子類創建線程的方法類似,由類A構造出來的線程的執行代碼就封裝在類A的成員方法run中。這裡編寫的run成員方法實現了對接口java.lang.Runnable的成員方法run的覆蓋。
在編寫玩實現接口java.lang.Runnable的類A之後,構造和啟動線程的方法如下:
A a = new A();
Thread t = new Thread(a);
T.start();
線程可以分為後台線程和用戶線程。後台線程在有些資料中也稱為守護線程或精靈線程。它和用戶線程的區別只是在於當在一個程序中只有後台線程在運行時,程序會立即退出。如果一個程序還存在正在運行的用戶線程,則該程序不會中止。因此,後台線程通常用來為其他線程提供服務。在默認情況下,線程是用戶線程。
一般通過類java.lang.Thread的成員方法public final boolean isDaemon()來判斷一個線程是用戶還是後台線程。通過類java,lang.Thread的成員方法public final void setDaemo(boolean on)可以將線程狀態在用戶線程和後台線程之間切換,在調用該方法時,一定要在public void start()方法被調用之前調用,否則將會報錯。
線程可以通過線程組(類java.lang.ThreadGroup的實例對象)來進行管理。這裡的線程組是一些線程和線程組的集合。因為線程組可以包含其他線程組,所以線程組實際上形成了一個樹狀的體系結構。除了樹狀結構根部的線程組之外,每個線程組都有一個父線程組。一個線程組的父線程組就是包含該線程組的線程組。
構造線程組可以通過類java.lang.ThreadGroup的兩個構造方法。其中,一個構造方法是:
public ThreadGroup(String name),它的參數name用來用來指定線程組的名稱,這時構造出來的線程組的父線程組就是當前線程所在的線程組。
另一個構造方法是:
public ThreadGroup(ThreadGroup parent,String name),其中參數parent指定父線程組,參數name指定新構造的線程組的名稱。
將一個線程添加到一個線程組中一般是在創建線程時通過線程的構造方法的參數指定線程組。例如,類java.lang.Thread的構造方法:
public Thread(ThreadGroup group,String name)的參數group指定所要添加到的線程組,參數name指定新創建的線程的名稱。
線程的生命周期基本上如下圖所示:
在正常的程序流程中,線程一般要經歷新生態、就緒態、運行態和死亡態這4個基本狀態。有時由於線程的並發等原因,還可能進入阻塞態。另外,根據程序的需要,線程還有可能進入等待態和睡眠態。
剛剛創建的線程還不能與其他線程一同並發運行。這時需要調用線程的成員方法start,使得線程進入就緒態。只有處於就緒態的線程才能參與Java虛擬機對線程的調度。Java虛擬機按照一定的調度規則讓一些處於就緒態的線程進入運行態。進入運行態的線程自動執行在線程的成員方法public void run()中的代碼。在執行run成員方法代碼之後,線程自動進入死亡態。
Java虛擬機對線程的調度首先要根據線程的優先級。每個線程都有優先級。Java虛擬機規定所有線程最小的優先級大小為1,最大為10。
在線程調度的過程中,如果存在多個處於就緒態的線程,則優先級高的線程優先進入運行態。如果存在資源共享沖突,則優先級高的線程優先占用該資源。如果線程的優先級都一樣,則Java虛擬機隨機調度這些線程進入運行運行態或占用資源。如果多個線程共享資源,並且只能有限個線程同時占用該資源,則Java虛擬機在調度就緒態的線程時會讓一些處於就緒態的線程占用這些資源,而其他處於就緒態的線程就會因為資源短缺而自動進入阻塞態。處於阻塞態的線程在所需要的資源准備就緒(例如其他線程退出這些資源)時會自動重新進入就緒態,再次由Java虛擬機進行調度。