程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> JAVA的多線程淺析

JAVA的多線程淺析

編輯:關於JAVA

一 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會更好。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved