摘要:本文在對Java多線程分析的基礎上,針對如何實現多線程,如何進行同步,如何管理多線程等問題進行了簡單的闡述。
關鍵詞:線程;多線程;線程組
中圖分類號:TP311文獻標識碼:A文章編號:1009-3044(2008)19-30181-03
Analyse Shallowly Java Multi-threading Mechanism
WANG Jun-yu, WANG Xian-hong
(Sanmenxia Vocational and Technical College, Sanmenxia 472000, China)
Abstract: This text is based on the analysis of Java multi-threading mechanism, aim at how to carry out a multi-threading, how to carry on synchronously, how to manage multi-threading's etc.'s problem to carry on to expound in brief.
Key words: thread; multi-threading; thread group
1 理解多線程
傳統的程序大多是單線程的,即一個程序只有一條從頭至尾的執行線索。然而現實世界中的很多過程都具有多條線索同時動作的特性。例如:我們可以一邊看書,一邊擺動胳膊,如果不容許這樣做,我們會感覺很難受。再如一個網絡服務器可能需要同時處理多個客戶機的請求等。
Java語言的一大特性就是內置對多線程的支持。多線程是這樣的一種機制,它允許在程序中并發執行多個指令流,每個指令流都稱為一個線程,彼此間互相獨立。線程又稱為輕量級進程,它和進程一樣擁有獨立的執行控制,由操作系統負責調度,區別在于線程沒有獨立的存儲空間,而是和所屬進程中的其它線程共享一個存儲空間,這使得線程間的通信遠較進程簡單。
多個線程是并發執行的,也就是在邏輯上“同時”,而不管是否是物理上的“同時”。若系統只有一個CPU,真正的“同時”是不可能的,但是由于CPU的速度非常快,用戶感覺不到其中的區別,因此我們也不用關心它,只需要設想各個線程是同時執行的即可。多線程和傳統的單線程在程序設計上最大的區別在于,由于各個線程的控制流彼此獨立,為了建立這些線程正在同步執行的感覺,Java快速地把控制從一個線程切換到另一個線程。
2 線程與程序、進程的區別
程序是一段靜態的代碼,它是應用軟件執行的藍本。
進程是程序的一次動態執行過程,它對應了從代碼加載、執行至執行完畢的一個完整過程,這個過程也是進程本身從產生、發展至消亡的過程。如果把銀行一天的工作比作一個進程,那么早上打鈴上班是進程的開始,晚上打下班鈴是進程的結束。
線程是比進程更小的執行單位。盡管線程在一些程序語言中又稱為進程,但是基本的思想是相同的:一個線程是一個運行在后臺的、獨立于主應用程序的任務[2]。所有的程序至少自動擁有一個線程。這個線程稱為主線程,當程序加載到內存中時,啟動主線程。要加載第二個、第三個或者第四個線程,程序就要使用Thread類和Runnable接口。
3 在Java中實現多線程
在Java中實現多線程有兩個途徑:繼承Thread類和實現Runnable接口。
3.1 繼承Thread類的多線程程序設計方法
Thread 類是JDK中定義的用于控制線程對象的類,在該類中封裝了用于進行線程控制的方法。見下面的示例代碼:
import java.util.*;
class TimePrinter extends Thread {
int pauseTime;
String name;
public TimePrinter(int x, String n) {
pauseTime = x;
name = n;
}
public void run() {
while(true) {
try {
System.out.println(name + \":\" + new
Date(System.currentTimeMillis()));
Thread.sleep(pauseTime);
} catch(Exception e) {
System.out.println(e);}}}
static public void main(String args[]) {
TimePrinter tp1 = new TimePrinter(1000, \"Fast Guy\");
tp1.start();
TimePrinter tp2 = new TimePrinter(3000, \"Slow Guy\");
tp2.start();}}
這種方法簡單明了,符合我們的習慣,但它有一個很大的缺點,那就是如果我們創建的類已經從一個類繼承(如小程序必須繼承自 Applet 類),那么無法再繼承 Thread 類,而這時我們又不想建立一個新的類,怎么辦呢?我們可以這樣做:就是不創建 Thread 類的子類,而是直接使用它,若直接使用Thread類,那就需要Runnable接口支持。雖然抽象類也可滿足,但需要繼承,為了避免繼承帶來的限制,則只有使用Java 提供的接口 java.lang.Runnable 來支持。
3.2 實現Runnable接口的多線程程序設計方法
Java語言中提供的另外一種實現多線程應用程序的方法是多線程對象實現Runnable接口并且在該類中定義用于啟動線程的run方法。這種定義方式的好處在于多線程應用對象可以繼承其它對象而不是必須繼承Thread類,從而能夠增加類定義的邏輯性。實現Runnable接口的多線程應用程序如下所示:
public class MyThread implements Runnable {
int count= 1, number;
public MyThread(int num) {
number = num;
System.out.println(\"創建線程 \" + number);
}
public void run() {
while(true) {
System.out.println(\"線程 \" + number + \":計數 \" + count);
if(++count== 6) return;
} }
public static void main(String args[]) {
for(int i = 0; i < 5; i++) new Thread(new MyThread(i+1)).start();}}
使用 Runnable 接口來實現多線程使得我們能夠在一個類中包容所有的代碼,有利于封裝,它的缺點在于,我們只能使用一套代碼,若想創建多個線程并使各個線程執行不同的代碼,則仍必須額外創建類,如果這樣的話,在大多數情況下也許還不如直接用多個類分別繼承 Thread 來得緊湊。這兩種方法,各有千秋,可靈活使用。
4 線程間的同步
由于同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問沖突這個嚴重的問題。比如一個工資管理負責人正在修改雇員的工資表,而一些雇員也正在領取工資,如果容許這樣做必然出現混亂。因此工資管理負責人正在修改工資表時(包括他喝茶休息一會),將不容許任何雇員領取工資,也就是說這些雇員必須等待。如在沒有多線程同步控制策略條件下的代碼:
public class SharedResouce {
private int a = 0;
private int b = 0;
public void setA(int a) { this.a = a; }
public void setB(int b) { this.b = b; }
}
由于未加鎖setA()時,可以setB(),setB()時可以setA()。這時兩個以上的線程同時執行,會引發沖突。因此在Java中定義了線程同步的概念,用synchronized關鍵字為共享資源加鎖來解決同步的問題,實現對共享資源的一致性維護。進行線程同步策略控制后的程序代碼如下所示:
public class SharedResouce {
private int a = 0;
private int b = 0;
public void synchronized setA(int a) { this.a = a; }
public void synchronized setB(int b) { this.b = b; }
}
同步整個方法,則setA()的時候無法setB(),setB()時無法setA()。也就是說,在任何一個時刻只能有一個線程訪問setA()或者setB()。
5 Java線程的管理
5.1 線程的狀態控制
要想實現多線程,必須在主線程中創建新的線程對象,新建的線程對象在它的一個完整的生命周期中通常要經歷5種狀態。在控制線程從一種狀態轉入另一種狀態時,必須調用正確的方法,如果調用的方法錯誤,就會產生一些異常[1]。5種狀態如下:
新建狀態:當一個Thread類或其子類的對象被聲明并創建時,新生的線程對象處于新建狀態。此時它已經有了相應的內存空間和其他資源。
就緒狀態:在處于新建狀態的線程中調用start方法將線程的狀態轉換為就緒狀態。這時,線程已經得到除CPU時間之外的其它系統資源,只等JVM的線程調度器按照線程的優先級對該線程進行調度,從而使該線程擁有能夠獲得CPU時間片的機會。
運行狀態:當就緒的線程被調度并獲得處理器資源時,便進入運行狀態。
阻塞狀態:一個正在運行的線程因某種原因不能繼續運行時,進入阻塞狀態。
死亡狀態:處于死亡狀態的線程不具有繼續運行的能力。線程死亡的原因有二,一個是正常運行的線程完成了它的全部工作,即執行完了run()方法的最后一個語句并退出,另一個是線程被提前強制性的終止。
5.2 線程的調度
線程調用的意義在于JVM應對運行的多個線程進行系統級的協調,以避免多個線程爭用有限資源而導致應用系統死機或者崩潰。
處于就緒狀態的線程首先進入就緒隊列排隊處理器資源,同一時刻在就緒隊列中的線程可能有多個。多線程系統會給每個線程自動分配一個線程的優先級,任務較緊急的重要線程,其優先級就較高;相反則較低。在線程排隊時,優先級高的線程可以排在較前的位置,能優先享用到處理器資源,而優先級較低的線程則只能等到排在它前面的高優先級線程執行完畢之后才能獲得處理器資源。對于優先級相同的線程,則遵循隊列的“先進先出”的原則,即先進入就緒狀態排隊的線程被優先分配到處理器資源,隨后才后進入隊列的線程服務。
當一個在就緒隊列中排隊的線程被分配到處理器資源而進入運行狀態之后,這個線程就稱為是被“調度”或被線程調度管理器選中了。線程調度管理器負責管理線程排隊和處理器在線程間的分配,一般都配有一個精心設計的線程調度算法。在Java系統中,線程調度依據優先級基礎上的“先到先服務”的原則。
5.3 線程分組管理
Java定義了在多線程運行系統中的線程組(ThreadGroup)對象,用于實現按照特定功能對線程進行集中式分組管理。用戶創建的每個線程均屬于某線程組,這個線程組可以在線程創建時指定,也可以不指定線程組以使該線程處于默認的線程組之中。但是,一旦線程加入某線程組,該線程就一直存在于該線程組中直至線程死亡,不能在中途改變線程所屬的線程組。
當Java的Application應用程序運行時,JVM創建名稱為main的線程組。除非單獨指定,在該應用程序中創建的線程均屬于main線程組。在main線程組中可以創建其它名稱的線程組并將其它線程加入到該線程組中,依此類推,構成線程和線程組之間的樹型管理和繼承關系。與線程類似,可以針對線程組對象進行線程組的調度、狀態管理以及優先級設置等。在對線程組進行管理過程中,加入到某線程組中的所有線程均被看作統一的對象。
6 小結
Java語言對應用程序多線程能力的支持增強了Java作為網絡程序設計語言的優勢,為實現分布式應用系統中多客戶端的并發訪問以及提高服務器的響應效率奠定堅實基礎。
參考文獻:
[1] 陳強, 孫建華, 等.Java程序設計[M].北京:人民郵電出版社,2001.12:125-137.
[2] (美)布雷恩·奧弗蘭,邁克爾·莫里森 著;劉偉,朱詩兵, 等譯. Java2精要.語言詳解與編程指南[M].北京:清華大學出版社,2002.9.