摘要:本文在對(duì)Java多線程分析的基礎(chǔ)上,針對(duì)如何實(shí)現(xiàn)多線程,如何進(jìn)行同步,如何管理多線程等問(wèn)題進(jìn)行了簡(jiǎn)單的闡述。
關(guān)鍵詞:線程;多線程;線程組
中圖分類號(hào):TP311文獻(xiàn)標(biāo)識(shí)碼:A文章編號(hào):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 理解多線程
傳統(tǒng)的程序大多是單線程的,即一個(gè)程序只有一條從頭至尾的執(zhí)行線索。然而現(xiàn)實(shí)世界中的很多過(guò)程都具有多條線索同時(shí)動(dòng)作的特性。例如:我們可以一邊看書,一邊擺動(dòng)胳膊,如果不容許這樣做,我們會(huì)感覺(jué)很難受。再如一個(gè)網(wǎng)絡(luò)服務(wù)器可能需要同時(shí)處理多個(gè)客戶機(jī)的請(qǐng)求等。
Java語(yǔ)言的一大特性就是內(nèi)置對(duì)多線程的支持。多線程是這樣的一種機(jī)制,它允許在程序中并發(fā)執(zhí)行多個(gè)指令流,每個(gè)指令流都稱為一個(gè)線程,彼此間互相獨(dú)立。線程又稱為輕量級(jí)進(jìn)程,它和進(jìn)程一樣擁有獨(dú)立的執(zhí)行控制,由操作系統(tǒng)負(fù)責(zé)調(diào)度,區(qū)別在于線程沒(méi)有獨(dú)立的存儲(chǔ)空間,而是和所屬進(jìn)程中的其它線程共享一個(gè)存儲(chǔ)空間,這使得線程間的通信遠(yuǎn)較進(jìn)程簡(jiǎn)單。
多個(gè)線程是并發(fā)執(zhí)行的,也就是在邏輯上“同時(shí)”,而不管是否是物理上的“同時(shí)”。若系統(tǒng)只有一個(gè)CPU,真正的“同時(shí)”是不可能的,但是由于CPU的速度非??欤脩舾杏X(jué)不到其中的區(qū)別,因此我們也不用關(guān)心它,只需要設(shè)想各個(gè)線程是同時(shí)執(zhí)行的即可。多線程和傳統(tǒng)的單線程在程序設(shè)計(jì)上最大的區(qū)別在于,由于各個(gè)線程的控制流彼此獨(dú)立,為了建立這些線程正在同步執(zhí)行的感覺(jué),Java快速地把控制從一個(gè)線程切換到另一個(gè)線程。
2 線程與程序、進(jìn)程的區(qū)別
程序是一段靜態(tài)的代碼,它是應(yīng)用軟件執(zhí)行的藍(lán)本。
進(jìn)程是程序的一次動(dòng)態(tài)執(zhí)行過(guò)程,它對(duì)應(yīng)了從代碼加載、執(zhí)行至執(zhí)行完畢的一個(gè)完整過(guò)程,這個(gè)過(guò)程也是進(jìn)程本身從產(chǎn)生、發(fā)展至消亡的過(guò)程。如果把銀行一天的工作比作一個(gè)進(jìn)程,那么早上打鈴上班是進(jìn)程的開始,晚上打下班鈴是進(jìn)程的結(jié)束。
線程是比進(jìn)程更小的執(zhí)行單位。盡管線程在一些程序語(yǔ)言中又稱為進(jìn)程,但是基本的思想是相同的:一個(gè)線程是一個(gè)運(yùn)行在后臺(tái)的、獨(dú)立于主應(yīng)用程序的任務(wù)[2]。所有的程序至少自動(dòng)擁有一個(gè)線程。這個(gè)線程稱為主線程,當(dāng)程序加載到內(nèi)存中時(shí),啟動(dòng)主線程。要加載第二個(gè)、第三個(gè)或者第四個(gè)線程,程序就要使用Thread類和Runnable接口。
3 在Java中實(shí)現(xiàn)多線程
在Java中實(shí)現(xiàn)多線程有兩個(gè)途徑:繼承Thread類和實(shí)現(xiàn)Runnable接口。
3.1 繼承Thread類的多線程程序設(shè)計(jì)方法
Thread 類是JDK中定義的用于控制線程對(duì)象的類,在該類中封裝了用于進(jìn)行線程控制的方法。見下面的示例代碼:
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();}}
這種方法簡(jiǎn)單明了,符合我們的習(xí)慣,但它有一個(gè)很大的缺點(diǎn),那就是如果我們創(chuàng)建的類已經(jīng)從一個(gè)類繼承(如小程序必須繼承自 Applet 類),那么無(wú)法再繼承 Thread 類,而這時(shí)我們又不想建立一個(gè)新的類,怎么辦呢?我們可以這樣做:就是不創(chuàng)建 Thread 類的子類,而是直接使用它,若直接使用Thread類,那就需要Runnable接口支持。雖然抽象類也可滿足,但需要繼承,為了避免繼承帶來(lái)的限制,則只有使用Java 提供的接口 java.lang.Runnable 來(lái)支持。
3.2 實(shí)現(xiàn)Runnable接口的多線程程序設(shè)計(jì)方法
Java語(yǔ)言中提供的另外一種實(shí)現(xiàn)多線程應(yīng)用程序的方法是多線程對(duì)象實(shí)現(xiàn)Runnable接口并且在該類中定義用于啟動(dòng)線程的run方法。這種定義方式的好處在于多線程應(yīng)用對(duì)象可以繼承其它對(duì)象而不是必須繼承Thread類,從而能夠增加類定義的邏輯性。實(shí)現(xiàn)Runnable接口的多線程應(yīng)用程序如下所示:
public class MyThread implements Runnable {
int count= 1, number;
public MyThread(int num) {
number = num;
System.out.println(\"創(chuàng)建線程 \" + number);
}
public void run() {
while(true) {
System.out.println(\"線程 \" + number + \":計(jì)數(shù) \" + 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 接口來(lái)實(shí)現(xiàn)多線程使得我們能夠在一個(gè)類中包容所有的代碼,有利于封裝,它的缺點(diǎn)在于,我們只能使用一套代碼,若想創(chuàng)建多個(gè)線程并使各個(gè)線程執(zhí)行不同的代碼,則仍必須額外創(chuàng)建類,如果這樣的話,在大多數(shù)情況下也許還不如直接用多個(gè)類分別繼承 Thread 來(lái)得緊湊。這兩種方法,各有千秋,可靈活使用。
4 線程間的同步
由于同一進(jìn)程的多個(gè)線程共享同一片存儲(chǔ)空間,在帶來(lái)方便的同時(shí),也帶來(lái)了訪問(wèn)沖突這個(gè)嚴(yán)重的問(wèn)題。比如一個(gè)工資管理負(fù)責(zé)人正在修改雇員的工資表,而一些雇員也正在領(lǐng)取工資,如果容許這樣做必然出現(xiàn)混亂。因此工資管理負(fù)責(zé)人正在修改工資表時(shí)(包括他喝茶休息一會(huì)),將不容許任何雇員領(lǐng)取工資,也就是說(shuō)這些雇員必須等待。如在沒(méi)有多線程同步控制策略條件下的代碼:
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()時(shí),可以setB(),setB()時(shí)可以setA()。這時(shí)兩個(gè)以上的線程同時(shí)執(zhí)行,會(huì)引發(fā)沖突。因此在Java中定義了線程同步的概念,用synchronized關(guān)鍵字為共享資源加鎖來(lái)解決同步的問(wèn)題,實(shí)現(xiàn)對(duì)共享資源的一致性維護(hù)。進(jìn)行線程同步策略控制后的程序代碼如下所示:
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; }
}
同步整個(gè)方法,則setA()的時(shí)候無(wú)法setB(),setB()時(shí)無(wú)法setA()。也就是說(shuō),在任何一個(gè)時(shí)刻只能有一個(gè)線程訪問(wèn)setA()或者setB()。
5 Java線程的管理
5.1 線程的狀態(tài)控制
要想實(shí)現(xiàn)多線程,必須在主線程中創(chuàng)建新的線程對(duì)象,新建的線程對(duì)象在它的一個(gè)完整的生命周期中通常要經(jīng)歷5種狀態(tài)。在控制線程從一種狀態(tài)轉(zhuǎn)入另一種狀態(tài)時(shí),必須調(diào)用正確的方法,如果調(diào)用的方法錯(cuò)誤,就會(huì)產(chǎn)生一些異常[1]。5種狀態(tài)如下:
新建狀態(tài):當(dāng)一個(gè)Thread類或其子類的對(duì)象被聲明并創(chuàng)建時(shí),新生的線程對(duì)象處于新建狀態(tài)。此時(shí)它已經(jīng)有了相應(yīng)的內(nèi)存空間和其他資源。
就緒狀態(tài):在處于新建狀態(tài)的線程中調(diào)用start方法將線程的狀態(tài)轉(zhuǎn)換為就緒狀態(tài)。這時(shí),線程已經(jīng)得到除CPU時(shí)間之外的其它系統(tǒng)資源,只等JVM的線程調(diào)度器按照線程的優(yōu)先級(jí)對(duì)該線程進(jìn)行調(diào)度,從而使該線程擁有能夠獲得CPU時(shí)間片的機(jī)會(huì)。
運(yùn)行狀態(tài):當(dāng)就緒的線程被調(diào)度并獲得處理器資源時(shí),便進(jìn)入運(yùn)行狀態(tài)。
阻塞狀態(tài):一個(gè)正在運(yùn)行的線程因某種原因不能繼續(xù)運(yùn)行時(shí),進(jìn)入阻塞狀態(tài)。
死亡狀態(tài):處于死亡狀態(tài)的線程不具有繼續(xù)運(yùn)行的能力。線程死亡的原因有二,一個(gè)是正常運(yùn)行的線程完成了它的全部工作,即執(zhí)行完了run()方法的最后一個(gè)語(yǔ)句并退出,另一個(gè)是線程被提前強(qiáng)制性的終止。
5.2 線程的調(diào)度
線程調(diào)用的意義在于JVM應(yīng)對(duì)運(yùn)行的多個(gè)線程進(jìn)行系統(tǒng)級(jí)的協(xié)調(diào),以避免多個(gè)線程爭(zhēng)用有限資源而導(dǎo)致應(yīng)用系統(tǒng)死機(jī)或者崩潰。
處于就緒狀態(tài)的線程首先進(jìn)入就緒隊(duì)列排隊(duì)處理器資源,同一時(shí)刻在就緒隊(duì)列中的線程可能有多個(gè)。多線程系統(tǒng)會(huì)給每個(gè)線程自動(dòng)分配一個(gè)線程的優(yōu)先級(jí),任務(wù)較緊急的重要線程,其優(yōu)先級(jí)就較高;相反則較低。在線程排隊(duì)時(shí),優(yōu)先級(jí)高的線程可以排在較前的位置,能優(yōu)先享用到處理器資源,而優(yōu)先級(jí)較低的線程則只能等到排在它前面的高優(yōu)先級(jí)線程執(zhí)行完畢之后才能獲得處理器資源。對(duì)于優(yōu)先級(jí)相同的線程,則遵循隊(duì)列的“先進(jìn)先出”的原則,即先進(jìn)入就緒狀態(tài)排隊(duì)的線程被優(yōu)先分配到處理器資源,隨后才后進(jìn)入隊(duì)列的線程服務(wù)。
當(dāng)一個(gè)在就緒隊(duì)列中排隊(duì)的線程被分配到處理器資源而進(jìn)入運(yùn)行狀態(tài)之后,這個(gè)線程就稱為是被“調(diào)度”或被線程調(diào)度管理器選中了。線程調(diào)度管理器負(fù)責(zé)管理線程排隊(duì)和處理器在線程間的分配,一般都配有一個(gè)精心設(shè)計(jì)的線程調(diào)度算法。在Java系統(tǒng)中,線程調(diào)度依據(jù)優(yōu)先級(jí)基礎(chǔ)上的“先到先服務(wù)”的原則。
5.3 線程分組管理
Java定義了在多線程運(yùn)行系統(tǒng)中的線程組(ThreadGroup)對(duì)象,用于實(shí)現(xiàn)按照特定功能對(duì)線程進(jìn)行集中式分組管理。用戶創(chuàng)建的每個(gè)線程均屬于某線程組,這個(gè)線程組可以在線程創(chuàng)建時(shí)指定,也可以不指定線程組以使該線程處于默認(rèn)的線程組之中。但是,一旦線程加入某線程組,該線程就一直存在于該線程組中直至線程死亡,不能在中途改變線程所屬的線程組。
當(dāng)Java的Application應(yīng)用程序運(yùn)行時(shí),JVM創(chuàng)建名稱為main的線程組。除非單獨(dú)指定,在該應(yīng)用程序中創(chuàng)建的線程均屬于main線程組。在main線程組中可以創(chuàng)建其它名稱的線程組并將其它線程加入到該線程組中,依此類推,構(gòu)成線程和線程組之間的樹型管理和繼承關(guān)系。與線程類似,可以針對(duì)線程組對(duì)象進(jìn)行線程組的調(diào)度、狀態(tài)管理以及優(yōu)先級(jí)設(shè)置等。在對(duì)線程組進(jìn)行管理過(guò)程中,加入到某線程組中的所有線程均被看作統(tǒng)一的對(duì)象。
6 小結(jié)
Java語(yǔ)言對(duì)應(yīng)用程序多線程能力的支持增強(qiáng)了Java作為網(wǎng)絡(luò)程序設(shè)計(jì)語(yǔ)言的優(yōu)勢(shì),為實(shí)現(xiàn)分布式應(yīng)用系統(tǒng)中多客戶端的并發(fā)訪問(wèn)以及提高服務(wù)器的響應(yīng)效率奠定堅(jiān)實(shí)基礎(chǔ)。
參考文獻(xiàn):
[1] 陳強(qiáng), 孫建華, 等.Java程序設(shè)計(jì)[M].北京:人民郵電出版社,2001.12:125-137.
[2] (美)布雷恩·奧弗蘭,邁克爾·莫里森 著;劉偉,朱詩(shī)兵, 等譯. Java2精要.語(yǔ)言詳解與編程指南[M].北京:清華大學(xué)出版社,2002.9.