JAVA 的多執行緒淺析(轉)

ba發表於2007-08-15
JAVA 的多執行緒淺析(轉)[@more@]· 一、JAVA 語言的來源、及特點
在這個高速資訊的時代,商家們紛紛把資訊、產品做到Internet國際互連網頁上。再這些不尋常網頁的背後,要屬功能齊全、安全可靠的程式語言,Java是當之無愧的。Java是由Sun Microsystem開發的一種功能強大的新型程式設計語言。是與平臺無關的程式語言。它是一種簡單的、面象物件的、分散式的、解釋的、鍵壯的、安全的、結構的中立的、可移植的、效能很優異的、多執行緒的、動態的、語言。
Java自問世以後,以其程式設計簡單、程式碼高效、可移植性強,很快受到了廣大計算機程式設計人士的青睞。Java語言是Internet上具有革命性的程式語言,它具有強大的動畫、多媒體和互動功能,他使World Web進入了一個全新的時代。Java語言與C++極為類似,可用它來建立安全的、可移植的、多執行緒的互動式程式。另外用Java開發出來的程式與平臺無關,可在多種平臺上執行。後臺開發,是一種高效、實用的程式設計方法。人們在螢幕前只能看到例如圖案、計算的結果等。實際上作業系統往往在後臺來排程一些事件、管理程式的流向等。例如作業系統中的堆疊,執行緒間的資源分配與管理,記憶體的建立、訪問、管理等。可謂舉不盛舉。下面就多執行緒來談一談。
· 二、JAVA的多執行緒理論
2.1引入
Java提供的多執行緒功能使得在一個程式裡可同時執行多個小任務。執行緒有時也稱小程式是一個大程式裡分出來的小的獨立的程式。因為Java實現的多執行緒技術,所以比C和C++更鍵壯。多執行緒帶來的更大的好處是更好的互動效能和實時控制效能。當然實時控制效能還取決於系統本身(UNIX, Windows,Macintosh等),在開發難易程度和效能上都比單執行緒要好。傳統程式設計環境通常是單執行緒的,由於JAVA是多執行緒的。儘管多執行緒是強大而靈巧的程式設計工具,但要用好卻不容易,且有許多陷阱,即使程式設計老手也難免誤用。為了更好的瞭解執行緒,用辦公室工作人員作比喻。辦公室工作人員就象 CPU,根據上級指示做工作,就象執行一個執行緒。在單執行緒環境中,每個程式編寫和執行的方式是任何時候程式只考慮一個處理順序。用我們的比喻,就象辦公室工作人員從頭到尾不受打擾和分心,只安排做一個工作。當然,實際生活中工作人員很難一次只有一個任務,更常見的是工作人員要同時做幾件事。老闆將工作交給工作人員,希望工作人員做一這個工作,再做點那個工作,等等。如果一個任務無法做下去了,比如工作人員等待另一部門的資訊,則工作人員將這個工作放在一邊,轉入另一個工作。一般來說,老闆希望工作人員手頭的各個任務每一天都有一些進展。這樣就引入了多執行緒的概念。多執行緒程式設計環境與這個典型的辦公室非常相似,同時給CPU分配了幾個任務或執行緒。和辦公室人員一樣,計算機CPU實際上不可能同一時間做幾件事,而是把時間分配到不同的執行緒,使每個執行緒都有點進展。如果一個執行緒無法進行,比如執行緒要求的鍵盤輸入尚未取得,則轉入另一執行緒的工作。通常,CPU線上程間的切換非常迅速,使人們感覺到好象所有執行緒是同時進行的。
任何處理環境,無論是單執行緒還是多執行緒,都有三個關鍵方面。第一個是CPU,它實際上進行計算機活動;第二個是執行的程式的程式碼;第三個是程式操作的資料.。
在多執行緒程式設計中,每個執行緒都用編碼提供執行緒的行為,用資料供給編碼操作。多個執行緒可以同時處理同一編碼和資料,不同的執行緒也可能各有不同的編碼和資料。事實上編碼和資料部分是相當獨立的,需要時即可向執行緒提供。因此經常是幾個執行緒使用同一編碼和不同的資料。這個思想也可以用辦公室工作人員來比喻。會計可能要做一個部門的帳或幾個或幾個部門的帳。任何情況的做帳的任務是相同的程式程式碼,但每個部門的資料是不同的。會計可能要做整個公司的帳,這時有幾個任務,但有些資料是共享的,因為公司帳需要來自各個部門的資料。
多執行緒程式設計環境用方便的模型隱藏CPU在任務切換間的事實。模型允許假裝成有多個可用的CPU。為了建立另一個任務,程式設計人員要求另一個虛擬CPU,指示它開始用某個資料組執行某個程式段。下面我們來建立執行緒。
建立執行緒
在JAVA中建立執行緒並不困難,所需要的三件事:執行的程式碼、程式碼所操作的資料和執行程式碼的虛擬CPU。虛擬CPU包裝在Thread類的例項中。建立Thread物件時,必須提供執行的程式碼和程式碼所處理的資料。JAVA的物件導向模型要求程式程式碼只能寫成類的成員方法。資料只能作為方法中的自動(或本地)變數或類的成員存在。這些規則要求為執行緒提供的程式碼和資料應以類的例項的形式出現。
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);
}
}
}
執行緒開始執行時,它在public void run()方法中執行。這種方法是定義的執行緒執行的起點,就象應用程式從main()開始、小程式從init()開始一樣。執行緒操作的本地資料是傳入執行緒的物件的成員。
首先,main()方法構造SimpleRunnable類的例項。注意,例項有自己的資料,這裡是一個String,初始化為”Hello”.由於例項r1傳入Thread類構造器,這是執行緒執行時處理的資料。執行的程式碼是例項方法run()。
2.2 執行緒的管理
單執行緒的程式都有一個main執行體,它執行一些程式碼,當程式結束執行後,它正好退出,程式同時結束執行。在JAVA中我們要得到相同的應答,必須稍微進行改動。只有當所有的執行緒退出後,程式才能結束。只要有一個執行緒一直在執行,程式就無法退出。執行緒包括四個狀態:new(開始),running (執行),wait(等候)和done(結束)。第一次建立執行緒時,都位於new狀態,在這個狀態下,不能執行執行緒,只能等待。然後,執行緒或者由方法 start開始或者送往done狀態,位於done中的執行緒已經結束執行,這是執行緒的最後一個狀態。一旦執行緒位於這個狀態,就不能再次出現,而且當 JAVA虛擬機器中的所有執行緒都位於done狀態時,程式就強行中止。當前正在執行的所有執行緒都位於running狀態,在程式之間用某種方法把處理器的執行時間分成時間片,位於running狀態的每個執行緒都是能執行的,但在一個給定的時間內,每個系統處理器只能執行一個執行緒。與位於running狀態的執行緒不同,由於某種原因,可以把已經位於waiting狀態的執行緒從一組可執行執行緒中刪除。如果執行緒的執行被中斷,就回到waiting狀態。用多種方法能中斷一個執行緒。執行緒能被掛起,在系統資源上等候,或者被告知進入休眠狀態。該狀態的執行緒可以返回到running狀態,也能由方法stop送入done 狀態,
方法
描述
有效狀態
目的狀態
Start()
開始執行一個執行緒
New
Running
Stop()
結束執行一個執行緒
New或running
Done
Sleep(long)
暫停一段時間,這個時間為給定的毫秒
Running
Wait
Sleep(long,int)
暫停片刻,可以精確到納秒
Running
Wait
Suspend()
掛起執行
Running
Wait
Resume()
恢復執行
Wait
Running
Yield()
明確放棄執行
Running
Running
2.3執行緒的排程
執行緒執行的順序以及從處理器中獲得的時間數量主要取決於開發者,處理器給每個執行緒分配一個時間片,而且執行緒的執行不能影響整個系統。處理器執行緒的系統或者是搶佔式的,或者是非搶佔式的。搶佔式系統在任何給定的時間內將執行最高優先順序的執行緒,系統中的所有執行緒都有自己的優先順序。 Thread.NORM_PRIORITY是執行緒的預設值,Thread類提供了setPriority和getPriority方法來設定和讀取優先權,使用setPriority方法能改變Java虛擬機器中的執行緒的重要性,它呼叫一個整數,類變數Thread.MIN_PRIORITY和 Thread.MAX_PRIORITY決定這個整數的有效範圍。Java虛擬機器是搶佔式的,它能保證執行優先順序最高的執行緒。在JAVA虛擬機器中我們把一個執行緒的優先順序改為最高,那麼他將取代當前正在執行的執行緒,除非這個執行緒結束執行或者被一條休眠命令放入waiting狀態,否者將一直佔用所有的處理器的時間。如果遇到兩個優先順序相同的執行緒,作業系統可能影響執行緒的執行順序。而且這個區別取決於時間片(time slicing)的概念。
管理幾個執行緒並不是真正的難題,對於上百個執行緒它是怎樣管理的呢?當然可以透過迴圈,來執行每一個執行緒,但是這顯然是冗長、乏味。JAVA建立了執行緒組。執行緒組是執行緒的一個譜系組,每個組包含的執行緒數不受限制,能對每個執行緒命名並能在整個執行緒組中執行(Suspend)和停止(Stop)這樣的操作。
2.4訊號標誌:保護其它共享資源
這種型別的保護被稱為互斥鎖。某個時間只能有一個執行緒讀取或修改這個資料值。在對檔案尤其是資訊資料庫進行處理時,讀取的資料總是多於寫資料,根據這個情況,可以簡化程式。下面舉一例,假設有一個僱員資訊的資料庫,其中包括僱員的地址和電話號碼等資訊,有時要進行修改,但要更多的還是讀資料,因此要儘可能防止資料被破壞或任意刪改。我們引入前面互斥鎖的概念,允許一個讀取鎖(red lock)和寫入鎖(write lock),可根據需要確定有權讀取資料的人員,而且當某人要寫資料時,必須有互斥鎖,這就是訊號標誌的概念。訊號標誌有兩種狀態,首先是empty() 狀態,表示沒有任何執行緒正在讀或寫,可以接受讀和寫的請求,並且立即提供服務;第二種狀態是reading()狀態,表示有執行緒正在從資料庫中讀資訊,並記錄進行讀操作的執行緒數,當它為0時,返回empty狀態,一個寫請求將導致這個執行緒進入等待狀態。
只能從empty狀態進入writing狀態,一旦進入writing狀態後,其它執行緒都不能寫操作,任何寫或讀請求都必須等到這個執行緒完成寫操作為止,而且waiting狀態中的程式也必須一直等到寫操作結束。完成操作後,返回到empty狀態,傳送一個通知訊號,等待的執行緒將得到服務。
下面實現了這個訊號標誌
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();
}
}
現在是測試訊號標誌的程式:
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
catch(InterruptedException e){;}
System.out.println("Unlocking readLock:"+getName());
sem.readUnlock();
}
else if(op
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類的四個例項開始,它是個執行緒,用來讀或寫一個共享檔案。Semaphore類保證訪問不會破壞檔案,執行程式,輸出結果如下:
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死鎖以及怎樣避免死鎖:
為了防止資料專案的併發訪問,應將資料專案標為專用,只有透過類本身的例項方法的同步區訪問。為了進入關鍵區,執行緒必須取得物件的鎖。假設執行緒要獨佔訪問兩個不同物件的資料,則必須從每個物件各取一個不同的鎖。現在假設另一個執行緒也要獨佔訪問這兩個物件,則該程式必須得到這兩把鎖之後才能進入。由於需要兩把鎖,程式設計如果不小心就可能出現死鎖。假設第一個執行緒取得物件A的鎖,準備取物件B的鎖,而第二個執行緒取得了物件B的鎖,準備取物件A的鎖,兩個執行緒都不能進入,因為兩者都不能離開進入的同步塊,既兩者都不能放棄目前持有的鎖。避免死鎖要認真設計。執行緒因為某個先決條件而受阻時,如需要鎖標記時,不能讓執行緒的停止本身禁止條件的變化。如果要取得多個資源,如兩個不同物件的鎖,必須定義取得資源的順序。如果物件A和B的鎖總是按字母順序取得,則不會出現前面說道的餓死條件。
· 三、Java多執行緒的優缺點
由於JAVA的多執行緒功能齊全,各種情況面面具到,它帶來的好處也是顯然易見的。多執行緒帶來的更大的好處是更好的互動效能和實時控制效能。當然實時控制效能還取決於系統本身(UNIX,Windows,Macintosh 等),在開發難易程度和效能上都比單執行緒要好。當然一個好的程式設計語言肯定也難免有不足之處。由於多執行緒還沒有充分利用基本OS的這一功能。這點我在前面已經提到,對於不同的系統,上面的程式可能會出現截然不同的結果,這使程式設計者偶會感到迷惑不解。希望在不久的將來JAVA的多執行緒能充分利用到作業系統,減少對程式設計者的困惑。我期待著JAVA會更好。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-958369/,如需轉載,請註明出處,否則將追究法律責任。

相關文章