一 JAVA 語言的來源、及特點(diǎn) 在這個高速信息的時代,商家們紛紛把信息、產(chǎn)品做到Internet國際互連網(wǎng)頁上。再這些不尋常網(wǎng)頁的背后,要屬功能齊全、安全可靠的編程語言,Java是當(dāng)之無愧的。Java是由Sun Microsystem開發(fā)的一種功能強(qiáng)大的新型程序設(shè)計語言。是與平臺無關(guān)的編程語言。它是一種簡單的、面象對象的、分布式的、解釋的、鍵壯的、安全的、結(jié)構(gòu)的中立的、可移植的、性能很優(yōu)異的、多線程的、動態(tài)的、語言。 Java自問世以后,以其編程簡單、代碼高效、可移植性強(qiáng),很快受到了廣大計算機(jī)編程人士的青睞。Java語言是Internet上具有革命性的編程語言,它具有強(qiáng)大的動畫、多媒體和交互功能,他使World Web進(jìn)入了一個全新的時代。Java語言與C++極為類似,可用它來創(chuàng)建安全的、可移植的、多線程的交互式程序。另外用Java開發(fā)出來的程序與平臺無關(guān),可在多種平臺上運(yùn)行。后臺開發(fā),是一種高效、實(shí)用的編程方法。人們在屏幕前只能看到例如圖案、計算的結(jié)果等。實(shí)際上操作系統(tǒng)往往在后臺來調(diào)度一些事件、管理程序的流向等。例如操作系統(tǒng)中的堆棧,線程間的資源分配與管理,內(nèi)存的創(chuàng)建、訪問、管理等。可謂舉不盛舉。下面就多線程來談一談。 二 JAVA的多線程理論 2.1引入 Java提供的多線程功能使得在一個程序里可同時執(zhí)行多個小任務(wù)。線程有時也稱小進(jìn)程是一個大進(jìn)程里分出來的小的獨(dú)立的進(jìn)程。因?yàn)镴ava實(shí)現(xiàn)的多線程技術(shù),所以比C和C++更鍵壯。多線程帶來的更大的好處是更好的交互性能和實(shí)時控制性能。當(dāng)然實(shí)時控制性能還取決于系統(tǒng)本身(UNIX,Windows,Macintosh等),在開發(fā)難易程度和性能上都比單線程要好。傳統(tǒng)編程環(huán)境通常是單線程的,由于JAVA是多線程的。盡管多線程是強(qiáng)大而靈巧的編程工具,但要用好卻不容易,且有許多陷阱,即使編程老手也難免誤用。為了更好的了解線程,用辦公室工作人員作比喻。辦公室工作人員就象CPU,根據(jù)上級指示做工作,就象執(zhí)行一個線程。在單線程環(huán)境中,每個程序編寫和執(zhí)行的方式是任何時候程序只考慮一個處理順序。用我們的比喻,就象辦公室工作人員從頭到尾不受打擾和分心,只安排做一個工作。當(dāng)然,實(shí)際生活中工作人員很難一次只有一個任務(wù),更常見的是工作人員要同時做幾件事。老板將工作交給工作人員,希望工作人員做一這個工作,再做點(diǎn)那個工作,等等。如果一個任務(wù)無法做下去了,比如工作人員等待另一部門的信息,則工作人員將這個工作放在一邊,轉(zhuǎn)入另一個工作。一般來說,老板希望工作人員手頭的各個任務(wù)每一天都有一些進(jìn)展。這樣就引入了多線程的概念。多線程編程環(huán)境與這個典型的辦公室非常相似,同時給CPU分配了幾個任務(wù)或線程。和辦公室人員一樣,計算機(jī)CPU實(shí)際上不可能同一時間做幾件事,而是把時間分配到不同的線程,使每個線程都有點(diǎn)進(jìn)展。如果一個線程無法進(jìn)行,比如線程要求的鍵盤輸入尚未取得,則轉(zhuǎn)入另一線程的工作。通常,CPU在線程間的切換非常迅速,使人們感覺到好象所有線程是同時進(jìn)行的。 任何處理環(huán)境,無論是單線程還是多線程,都有三個關(guān)鍵方面。第一個是CPU,它實(shí)際上進(jìn)行計算機(jī)活動;第二個是執(zhí)行的程序的代碼;第三個是程序操作的數(shù)據(jù)。 在多線程編程中,每個線程都用編碼提供線程的行為,用數(shù)據(jù)供給編碼操作。多個線程可以同時處理同一編碼和數(shù)據(jù),不同的線程也可能各有不同的編碼和數(shù)據(jù)。事實(shí)上編碼和數(shù)據(jù)部分是相當(dāng)獨(dú)立的,需要時即可向線程提供。因此經(jīng)常是幾個線程使用同一編碼和不同的數(shù)據(jù)。這個思想也可以用辦公室工作人員來比喻。會計可能要做一個部門的帳或幾個或幾個部門的帳。任何情況的做帳的任務(wù)是相同的程序代碼,但每個部門的數(shù)據(jù)是不同的。會計可能要做整個公司的帳,這時有幾個任務(wù),但有些數(shù)據(jù)是共享的,因?yàn)楣編ば枰獊碜愿鱾部門的數(shù)據(jù)。 多線程編程環(huán)境用方便的模型隱藏CPU在任務(wù)切換間的事實(shí)。模型允許假裝成有多個可用的CPU。為了建立另一個任務(wù),編程人員要求另一個虛擬CPU,指示它開始用某個數(shù)據(jù)組執(zhí)行某個程序段。下面我們來建立線程。 建立線程 在JAVA中建立線程并不困難,所需要的三件事:執(zhí)行的代碼、代碼所操作的數(shù)據(jù)和執(zhí)行代碼的虛擬CPU。虛擬CPU包裝在Thread類的實(shí)例中。建立Thread對象時,必須提供執(zhí)行的代碼和代碼所處理的數(shù)據(jù)。JAVA的面向?qū)ο竽P鸵蟪绦虼a只能寫成類的成員方法。數(shù)據(jù)只能作為方法中的自動(或本地)變量或類的成員存在。這些規(guī)則要求為線程提供的代碼和數(shù)據(jù)應(yīng)以類的實(shí)例的形式出現(xiàn)。 Public class SimpleRunnable implemants Runable{ Private String message; Public static void main(String args[]){ SimpleRunnable r1=new SimpleRunnable(“Hello”); Thread t1=new Thread(r1); t1.start(); } public SimpleRunnable(String message){ this.message=message; } public void run(){ for(;;){ System.out.println(message); }
}
}
線程開始執(zhí)行時,它在public void run()方法中執(zhí)行。這種方法是定義的線程執(zhí)行的起點(diǎn),就象應(yīng)用程序從main()開始、小程序從init()開始一樣。線程操作的本地數(shù)據(jù)是傳入線程的對象的成員。
首先,main()方法構(gòu)造SimpleRunnable類的實(shí)例。注意,實(shí)例有自己的數(shù)據(jù),這里是一個String,初始化為”Hello”.由于實(shí)例r1傳入Thread類構(gòu)造器,這是線程運(yùn)行時處理的數(shù)據(jù)。執(zhí)行的代碼是實(shí)例方法run()。
2.2 線程的管理
單線程的程序都有一個main執(zhí)行體,它運(yùn)行一些代碼,當(dāng)程序結(jié)束執(zhí)行后,它正好退出,程序同時結(jié)束運(yùn)行。在JAVA中我們要得到相同的應(yīng)答,必須稍微進(jìn)行改動。只有當(dāng)所有的線程退出后,程序才能結(jié)束。只要有一個線程一直在運(yùn)行,程序就無法退出。線程包括四個狀態(tài):new(開始),running(運(yùn)行),wait(等候)和done(結(jié)束)。第一次創(chuàng)建線程時,都位于new狀態(tài),在這個狀態(tài)下,不能運(yùn)行線程,只能等待。然后,線程或者由方法start開始或者送往done狀態(tài),位于done中的線程已經(jīng)結(jié)束執(zhí)行,這是線程的最后一個狀態(tài)。一旦線程位于這個狀態(tài),就不能再次出現(xiàn),而且當(dāng)JAVA虛擬機(jī)中的所有線程都位于done狀態(tài)時,程序就強(qiáng)行中止。當(dāng)前正在執(zhí)行的所有線程都位于running狀態(tài),在程序之間用某種方法把處理器的執(zhí)行時間分成時間片,位于running狀態(tài)的每個線程都是能運(yùn)行的,但在一個給定的時間內(nèi),每個系統(tǒng)處理器只能運(yùn)行一個線程。與位于running狀態(tài)的線程不同,由于某種原因,可以把已經(jīng)位于waiting狀態(tài)的線程從一組可執(zhí)行線程中刪除。如果線程的執(zhí)行被中斷,就回到waiting狀態(tài)。用多種方法能中斷一個線程。線程能被掛起,在系統(tǒng)資源上等候,或者被告知進(jìn)入休眠狀態(tài)。該狀態(tài)的線程可以返回到running狀態(tài),也能由方法stop送入done狀態(tài),
方法 描述 有效狀態(tài) 目的狀態(tài)
Start() 開始執(zhí)行一個線程 New Running
Stop() 結(jié)束執(zhí)行一個線程 New或running Done
Sleep(long) 暫停一段時間,這個時間為給定的毫秒 Running Wait
Sleep(long,int) 暫停片刻,可以精確到納秒 Running Wait
Suspend() 掛起執(zhí)行 Running Wait
Resume() 恢復(fù)執(zhí)行 Wait Running
Yield() 明確放棄執(zhí)行 Running Running
2.3線程的調(diào)度
線程運(yùn)行的順序以及從處理器中獲得的時間數(shù)量主要取決于開發(fā)者,處理器給每個線程分配一個時間片,而且線程的運(yùn)行不能影響整個系統(tǒng)。處理器線程的系統(tǒng)或者是搶占式的,或者是非搶占式的。搶占式系統(tǒng)在任何給定的時間內(nèi)將運(yùn)行最高優(yōu)先級的線程,系統(tǒng)中的所有線程都有自己的優(yōu)先級。Thread.NORM_PRIORITY是線程的缺省值,Thread類提供了setPriority和getPriority方法來設(shè)置和讀取優(yōu)先權(quán),使用setPriority方法能改變Java虛擬機(jī)中的線程的重要性,它調(diào)用一個整數(shù),類變量Thread.MIN_PRIORITY和Thread.MAX_PRIORITY決定這個整數(shù)的有效范圍。Java虛擬機(jī)是搶占式的,它能保證運(yùn)行優(yōu)先級最高的線程。在JAVA虛擬機(jī)中我們把一個線程的優(yōu)先級改為最高,那么他將取代當(dāng)前正在運(yùn)行的線程,除非這個線程結(jié)束運(yùn)行或者被一條休眠命令放入waiting狀態(tài),否者將一直占用所有的處理器的時間。如果遇到兩個優(yōu)先級相同的線程,操作系統(tǒng)可能影響線程的執(zhí)行順序。而且這個區(qū)別取決于時間片(time slicing)的概念。
管理幾個線程并不是真正的難題,對于上百個線程它是怎樣管理的呢?當(dāng)然可以通過循環(huán),來執(zhí)行每一個線程,但是這顯然是冗長、乏味。JAVA創(chuàng)建了線程組。線程組是線程的一個譜系組,每個組包含的線程數(shù)不受限制,能對每個線程命名并能在整個線程組中執(zhí)行(Suspend)和停止(Stop)這樣的操作。
2.4信號標(biāo)志:保護(hù)其它共享資源
這種類型的保護(hù)被稱為互斥鎖。某個時間只能有一個線程讀取或修改這個數(shù)據(jù)值。在對文件尤其是信息數(shù)據(jù)庫進(jìn)行處理時,讀取的數(shù)據(jù)總是多于寫數(shù)據(jù),根據(jù)這個情況,可以簡化程序。下面舉一例,假設(shè)有一個雇員信息的數(shù)據(jù)庫,其中包括雇員的地址和電話號碼等信息,有時要進(jìn)行修改,但要更多的還是讀數(shù)據(jù),因此要盡可能防止數(shù)據(jù)被破壞或任意刪改。我們引入前面互斥鎖的概念,允許一個讀取鎖(red lock)和寫入鎖(write lock),可根據(jù)需要確定有權(quán)讀取數(shù)據(jù)的人員,而且當(dāng)某人要寫數(shù)據(jù)時,必須有互斥鎖,這就是信號標(biāo)志的概念。信號標(biāo)志有兩種狀態(tài),首先是empty()狀態(tài),表示沒有任何線程正在讀或?qū),可以接受讀和寫的請求,并且立即提供服務(wù);第二種狀態(tài)是reading()狀態(tài),表示有線程正在從數(shù)據(jù)庫中讀信息,并記錄進(jìn)行讀操作的線程數(shù),當(dāng)它為0時,返回empty狀態(tài),一個寫請求將導(dǎo)致這個線程進(jìn)入等待狀態(tài)。
只能從empty狀態(tài)進(jìn)入writing狀態(tài),一旦進(jìn)入writing狀態(tài)后,其它線程都不能寫操作,任何寫或讀請求都必須等到這個線程完成寫操作為止,而且waiting狀態(tài)中的進(jìn)程也必須一直等到寫操作結(jié)束。完成操作后,返回到empty狀態(tài),發(fā)送一個通知信號,等待的線程將得到服務(wù)。
下面實(shí)現(xiàn)了這個信號標(biāo)志
class Semaphore{
final static int EMPTY=0;
final static int READING=1;
final static int WRITING=2;
protected int state=EMPTY;
protected int readCnt=0;
public synchronized void readLock(){
if(state==EMPTY){
state=READING;
}
else if(state==READING){
}
else if(state==WRITING){
while(state==WRITING){
try {wait();}
catch(InterruptedException e){;}
}
state=READING;
}
readCnt++;
return;
}
public synchronized void writeLock(){
if(state==EMPTY){
state=WRITING;
}
else{
while(state!=EMPTY){
try {wait();}
catch(InterruptedException e) {;}
}
}
}
public synchronized void readUnlock(){
readCnt--;
if(readCnt==0){
state=EMPTY;
notify();
}
}
public synchronized void writeUnlock(){
state=EMPTY;
notify();
}
}
現(xiàn)在是測試信號標(biāo)志的程序:
class Process extends Thread{
String op;
Semaphore sem;
Process(String name,String op,Semaphore sem){
super(name);
this.op=op;
this.sem=sem;
start();
}
public void run(){
if(op.compareTo("read")==0){
System.out.println("Trying to get readLock:"+getName());
sem.readLock();
System.out.println("Read op:"+getName());
try {sleep((int)(Math.random()*50));}
catch(InterruptedException e){;}
System.out.println("Unlocking readLock:"+getName());
sem.readUnlock();
}
else if(op.compareTo("write")==0){
System.out.println("Trying to get writeLock:"+getName());
sem.writeLock();
System.out.println("Write op:"+getName());
try {sleep((int)(Math.random()*50));}
catch(InterruptedException e){;}
System.out.println("Unlocking writeLock:"+getName());
sem.writeUnlock();
}
}
}
public class testSem{
public static void main(String argv[]){
Semaphore lock = new Semaphore();
new Process("1","read",lock);
new Process("2","read",lock);
new Process("3","write",lock);
new Process("4","read",lock);
}
}
testSem 類從process類的四個實(shí)例開始,它是個線程,用來讀或?qū)懸粋共享文
件。Semaphore類保證訪問不會破壞文件,執(zhí)行程序,輸出結(jié)果如下:
Trying to get readLock:1
Read op:1
Trying to get readLock:2
Read op:2
Trying to get writeLock:3
Trying to get readLock:4
Read op:4
Unlocking readLock:1
Unlocking readLock:2
Unlocking readLock:4
Write op:3
Unlocking writeLock:3
從這可看到,
2.5死鎖以及怎樣避免死鎖:
為了防止數(shù)據(jù)項目的并發(fā)訪問,應(yīng)將數(shù)據(jù)項目標(biāo)為專用,只有通過類本身的實(shí)例方法的同步區(qū)訪問。為了進(jìn)入關(guān)鍵區(qū),線程必須取得對象的鎖。假設(shè)線程要獨(dú)占訪問兩個不同對象的數(shù)據(jù),則必須從每個對象各取一個不同的鎖,F(xiàn)在假設(shè)另一個線程也要獨(dú)占訪問這兩個對象,則該進(jìn)程必須得到這兩把鎖之后才能進(jìn)入。由于需要兩把鎖,編程如果不小心就可能出現(xiàn)死鎖。假設(shè)第一個線程取得對象A的鎖,準(zhǔn)備取對象B的鎖,而第二個線程取得了對象B的鎖,準(zhǔn)備取對象A的鎖,兩個線程都不能進(jìn)入,因?yàn)閮烧叨疾荒茈x開進(jìn)入的同步塊,既兩者都不能放棄目前持有的鎖。避免死鎖要認(rèn)真設(shè)計。線程因?yàn)槟硞先決條件而受阻時,如需要鎖標(biāo)記時,不能讓線程的停止本身禁止條件的變化。如果要取得多個資源,如兩個不同對象的鎖,必須定義取得資源的順序。如果對象A和B的鎖總是按字母順序取得,則不會出現(xiàn)前面說道的餓死條件。
三Java多線程的優(yōu)缺點(diǎn)
由于JAVA的多線程功能齊全,各種情況面面具到,它帶來的好處也是顯然易見的。多線程帶來的更大的好處是更好的交互性能和實(shí)時控制性能。當(dāng)然實(shí)時控制性能還取決于系統(tǒng)本身(UNIX,Windows,Macintosh 等),在開發(fā)難易程度和性能上都比單線程要好。當(dāng)然一個好的程序設(shè)計語言肯定也難免有不足之處。由于多線程還沒有充分利用基本OS的這一功能。這點(diǎn)我在前面已經(jīng)提到,對于不同的系統(tǒng),上面的程序可能會出現(xiàn)截然不同的結(jié)果,這使編程者偶會感到迷惑不解。希望在不久的將來JAVA的多線程能充分利用到操作系統(tǒng),減少對編程者的困惑。我期待著JAVA會更好。
|