蘋果的成長日記

          我還是個青蘋果呀!

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            57 隨筆 :: 0 文章 :: 74 評論 :: 0 Trackbacks
          JAVA教程:解析Java的多線程機制
          http://dev.21tx.com 2005年05月03日 賽迪網

          一、進程與應用程序的區別
            
            進程(Process)是最初定義在Unix等多用戶、多任務操作系統環境下用于表示應用程序在內存環境中基本執行單元的概念。以Unix操作系統為例,進程是Unix操作系統環境中的基本成分、是系統資源分配的基本單位。Unix操作系統中完成的幾乎所有用戶管理和資源分配等工作都是通過操作系統對應用程序進程的控制來實現的。
            
            C、C++、Java等語言編寫的源程序經相應的編譯器編譯成可執行文件后,提交給計算機處理器運行。這時,處在可執行狀態中的應用程序稱為進程。從用戶角度來看,進程是應用程序的一個執行過程。從操作系統核心角度來看,進程代表的是操作系統分配的內存、CPU時間片等資源的基本單位,是為正在運行的程序提供的運行環境。進程與應用程序的區別在于應用程序作為一個靜態文件存儲在計算機系統的硬盤等存儲空間中,而進程則是處于動態條件下由操作系統維護的系統資源管理實體。多任務環境下應用程序進程的主要特點包括:
            
            ●進程在執行過程中有內存單元的初始入口點,并且進程存活過程中始終擁有獨立的內存地址空間;
            
            ●進程的生存期狀態包括創建、就緒、運行、阻塞和死亡等類型;
            
            ●從應用程序進程在執行過程中向CPU發出的運行指令形式不同,可以將進程的狀態分為用戶態和核心態。處于用戶態下的進程執行的是應用程序指令、處于核心態下的應用程序進程執行的是操作系統指令。
            
            在Unix操作系統啟動過程中,系統自動創建swapper、init等系統進程,用于管理內存資源以及對用戶進程進行調度等。在Unix環境下無論是由操作系統創建的進程還要由應用程序執行創建的進程,均擁有唯一的進程標識(PID)。

          二、進程與Java線程的區別
            
            
            應用程序在執行過程中存在一個內存空間的初始入口點地址、一個程序執行過程中的代碼執行序列以及用于標識進程結束的內存出口點地址,在進程執行過程中的每一時間點均有唯一的處理器指令與內存單元地址相對應。
            
            Java語言中定義的線程(Thread)同樣包括一個內存入口點地址、一個出口點地址以及能夠順序執行的代碼序列。但是進程與線程的重要區別在于線程不能夠單獨執行,它必須運行在處于活動狀態的應用程序進程中,因此可以定義線程是程序內部的具有并發性的順序代碼流。
            
            Unix操作系統和Microsoft Windows操作系統支持多用戶、多進程的并發執行,而Java語言支持應用程序進程內部的多個執行線程的并發執行。多線程的意義在于一個應用程序的多個邏輯單元可以并發地執行。但是多線程并不意味著多個用戶進程在執行,操作系統也不把每個線程作為獨立的進程來分配獨立的系統資源。進程可以創建其子進程,子進程與父進程擁有不同的可執行代碼和數據內存空間。而在用于代表應用程序的進程中多個線程共享數據內存空間,但保持每個線程擁有獨立的執行堆棧和程序執行上下文(Context)。
            
            基于上述區別,線程也可以稱為輕型進程 (Light Weight Process,LWP)。不同線程間允許任務協作和數據交換,使得在計算機系統資源消耗等方面非常廉價。
            
            線程需要操作系統的支持,不是所有類型的計算機都支持多線程應用程序。Java程序設計語言將線程支持與語言運行環境結合在一起,提供了多任務并發執行的能力。這就好比一個人在處理家務的過程中,將衣服放到洗衣機中自動洗滌后將大米放在電飯鍋里,然后開始做菜。等菜做好了,飯熟了同時衣服也洗好了。
            
            需要注意的是:在應用程序中使用多線程不會增加 CPU 的數據處理能力。只有在多CPU 的計算機或者在網絡計算體系結構下,將Java程序劃分為多個并發執行線程后,同時啟動多個線程運行,使不同的線程運行在基于不同處理器的Java虛擬機中,才能提高應用程序的執行效率。

          另外,如果應用程序必須等待網絡連接或數據庫連接等數據吞吐速度相對較慢的資源時,多線程應用程序是非常有利的。基于Internet的應用程序有必要是多線程類型的,例如,當開發要支持大量客戶機的服務器端應用程序時,可以將應用程序創建成多線程形式來響應客戶端的連接請求,使每個連接用戶獨占一個客戶端連接線程。這樣,用戶感覺服務器只為連接用戶自己服務,從而縮短了服務器的客戶端響應時間。
            
            
          三、Java語言的多線程程序設計方法
            
            
            利用Java語言實現多線程應用程序的方法很簡單。根據多線程應用程序繼承或實現對象的不同可以采用兩種方式:一種是應用程序的并發運行對象直接繼承Java的線程類Thread;另外一種方式是定義并發執行對象實現Runnable接口。
            
            繼承Thread類的多線程程序設計方法
            
            Thread 類是JDK中定義的用于控制線程對象的類,在該類中封裝了用于進行線程控制的方法。見下面的示例代碼:
            
            [code]//Consumer.java
            import java.util.*;
            class Consumer extends Thread
            {
             int nTime;
             String strConsumer;
             public Consumer(int nTime, String strConsumer)
             {
             this.nTime = nTime;
             this.strConsumer = strConsumer;
             }
             public void run()
             {
            while(true)
            {
             try
            {
             System.out.println("Consumer name:"+strConsumer+"\n");
             Thread.sleep(nTime);
             }
            catch(Exception e)
            {
             e.printStackTrace();
             }
            }
             }
            static public void main(String args[])
            {
             Consumer aConsumer = new Consumer (1000, "aConsumer");
             aConsumer.start();
             Consumer bConsumer = new Consumer (2000, "bConsumer");
             bConsumer.start();
             Consumer cConsumer = new Consumer (3000, "cConsumer ");
             cConsumer.start();
            }
            } [/code]
            
            
            
            
            從上面的程序代碼可以看出:多線程執行地下Consumer繼承Java語言中的線程類Thread并且在main方法中創建了三個Consumer對象的實例。當調用對象實例的start方法時,自動調用Consumer類中定義的run方法啟動對象線程運行。線程運行的結果是每間隔nTime時間打印出對象實例中的字符串成員變量strConsumer的內容。
            
            可以總結出繼承Thread類的多線程程序設計方法是使應用程序類繼承Thread類并且在該類的run方法中實現并發性處理過程。
            
            實現Runnable接口的多線程程序設計方法
            
            Java語言中提供的另外一種實現多線程應用程序的方法是多線程對象實現Runnable接口并且在該類中定義用于啟動線程的run方法。這種定義方式的好處在于多線程應用對象可以繼承其它對象而不是必須繼承Thread類,從而能夠增加類定義的邏輯性。
            
            實現Runnable接口的多線程應用程序框架代碼如下所示:
            
            //Consumer.java
            import java.util.*;
            class Consumer implements Runnable
            {
             … …
            public Consumer(int nTime, String strConsumer){… …}
            public void run(){… …}
            static public void main(String args[])
            {
            Thread aConsumer = new Thread(new Consumer(1000, "aConsumer"));
            aConsumer.start();
            //其它對象實例的運行線程
             //… …
             }
            }
            
            從上述代碼可以看出:該類實現了Runnable接口并且在該類中定義了run方法。這種多線程應用程序的實現方式與繼承Thread類的多線程應用程序的重要區別在于啟動多線程對象的方法設計方法不同。在上述代碼中,通過創建Thread對象實例并且將應用對象作為創建Thread類實例的參數。

          四、線程間的同步
            
            Java應用程序的多個線程共享同一進程的數據資源,多個用戶線程在并發運行過程中可能同時訪問具有敏感性的內容。在Java中定義了線程同步的概念,實現對共享資源的一致性維護。下面以筆者最近開發的移動通信計費系統中線程間同步控制方法,說明Java語言中多線程同步方式的實現過程。
            
            在沒有多線程同步控制策略條件下的客戶賬戶類定義框架代碼如下所示:
            
            public class RegisterAccount
            {
            float fBalance;
            //客戶繳費方法
            public void deposit(float fFees){ fBalance += fFees; }
            //通話計費方法
            public void withdraw(float fFees){ fBalance -= fFees; }
            … …
            }

            
            
            
            
            讀者也許會認為:上述程序代碼完全能夠滿足計費系統實際的需要。確實,在單線程環境下該程序確實是可靠的。但是,多進程并發運行的情況是怎樣的呢?假設發生這種情況:客戶在客戶服務中心進行繳費的同時正在利用移動通信設備進行通話,客戶通話結束時計費系統啟動計費進程,而同時服務中心的工作人員也提交繳費進程運行。讀者可以看到如果發生這種情況,對客戶賬戶的處理是不嚴肅的。
            
            如何解決這種問題呢?很簡單,在RegisterAccount類方法定義中加上用于標識同步方法的關鍵字synchronized。這樣,在同步方法執行過程中該方法涉及的共享資源(在上述代碼中為fBalance成員變量)將被加上共享鎖以確保在方法運行期間只有該方法能夠對共享資源進行訪問,直到該方法的線程運行結束打開共享鎖,其它線程才能夠訪問這些共享資源。在共享鎖沒有打開的時候其它訪問共享資源的線程處于阻塞狀態。
            
            進行線程同步策略控制后的RegisterAccount類定義如下面代碼所示:
            
            public class RegisterAccount
            {
            float fBalance;
            public synchronized void deposit(float fFees){ fBalance += fFees; }
            public synchronized void withdraw(float fFees){ fBalance -= fFees; }
            … …
            }

            
            從經過線程同步機制定義后的代碼形式可以看出:在對共享資源進行訪問的方法訪問屬性關鍵字(public)后附加同步定義關鍵字synchronized,使得同步方法在對共享資源訪問的時候,為這些敏感資源附加共享鎖來控制方法執行期間的資源獨占性,實現了應用系統數據資源的一致性管理和維護。


          五、 Java線程的管理
            
            
            線程的狀態控制
            
            在這里需要明確的是:無論采用繼承Thread類還是實現Runnable接口來實現應用程序的多線程能力,都需要在該類中定義用于完成實際功能的run方法,這個run方法稱為線程體(Thread Body)。按照線程體在計算機系統內存中的狀態不同,可以將線程分為創建、就緒、運行、睡眠、掛起和死亡等類型。這些線程狀態類型下線程的特征為:
            
            創建狀態:當利用new關鍵字創建線程對象實例后,它僅僅作為一個對象實例存在,JVM沒有為其分配CPU時間片等線程運行資源;
            
            就緒狀態:在處于創建狀態的線程中調用start方法將線程的狀態轉換為就緒狀態。這時,線程已經得到除CPU時間之外的其它系統資源,只等JVM的線程調度器按照線程的優先級對該線程進行調度,從而使該線程擁有能夠獲得CPU時間片的機會。
            
            睡眠狀態:在線程運行過程中可以調用sleep方法并在方法參數中指定線程的睡眠時間將線程狀態轉換為睡眠狀態。這時,該線程在不釋放占用資源的情況下停止運行指定的睡眠時間。時間到達后,線程重新由JVM線程調度器進行調度和管理。
            
            掛起狀態:可以通過調用suspend方法將線程的狀態轉換為掛起狀態。這時,線程將釋放占用的所有資源,由JVM調度轉入臨時存儲空間,直至應用程序調用resume方法恢復線程運行。
            
            死亡狀態:當線程體運行結束或者調用線程對象的stop方法后線程將終止運行,由JVM收回線程占用的資源。
            
            在Java線程類中分別定義了相應的方法,用于在應用程序中對線程狀態進行控制和管理。
            
            線程的調度
            
            線程調用的意義在于JVM應對運行的多個線程進行系統級的協調,以避免多個線程爭用有限資源而導致應用系統死機或者崩潰。
            
            為了線程對于操作系統和用戶的重要性區分開,Java定義了線程的優先級策略。Java將線程的優先級分為10個等級,分別用1-10之間的數字表示。數字越大表明線程的級別越高。相應地,在Thread類中定義了表示線程最低、最高和普通優先級的成員變量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,代表的優先級等級分別為1、10和5。當一個線程對象被創建時,其默認的線程優先級是5。
            
            為了控制線程的運行策略,Java定義了線程調度器來監控系統中處于就緒狀態的所有線程。線程調度器按照線程的優先級決定那個線程投入處理器運行。在多個線程處于就緒狀態的條件下,具有高優先級的線程會在低優先級線程之前得到執行。線程調度器同樣采用"搶占式"策略來調度線程執行,即當前線程執行過程中有較高優先級的線程進入就緒狀態,則高優先級的線程立即被調度執行。具有相同優先級的所有線程采用輪轉的方式來共同分配CPU時間片。
            
            在應用程序中設置線程優先級的方法很簡單,在創建線程對象之后可以調用線程對象的setPriority方法改變該線程的運行優先級,同樣可以調用getPriority方法獲取當前線程的優先級。
            
            在Java中比較特殊的線程是被稱為守護(Daemon)線程的低級別線程。這個線程具有最低的優先級,用于為系統中的其它對象和線程提供服務。將一個用戶線程設置為守護線程的方式是在線程對象創建之前調用線程對象的setDaemon方法。典型的守護線程例子是JVM中的系統資源自動回收線程,它始終在低級別的狀態中運行,用于實時監控和管理系統中的可回收資源。
            
            線程分組管理
            
            Java定義了在多線程運行系統中的線程組(ThreadGroup)對象,用于實現按照特定功能對線程進行集中式分組管理。用戶創建的每個線程均屬于某線程組,這個線程組可以在線程創建時指定,也可以不指定線程組以使該線程處于默認的線程組之中。但是,一旦線程加入某線程組,該線程就一直存在于該線程組中直至線程死亡,不能在中途改變線程所屬的線程組。
            
            當Java的Application應用程序運行時,JVM創建名稱為main的線程組。除非單獨指定,在該應用程序中創建的線程均屬于main線程組。在main線程組中可以創建其它名稱的線程組并將其它線程加入到該線程組中,依此類推,構成線程和線程組之間的樹型管理和繼承關系。
            
            與線程類似,可以針對線程組對象進行線程組的調度、狀態管理以及優先級設置等。在對線程組進行管理過程中,加入到某線程組中的所有線程均被看作統一的對象。

          六、小結:
          本文針對Java平臺中線程的性質和應用程序的多線程策略進行了分析和講解。
            
            與其它操作系統環境不同,Java運行環境中的線程類似于多用戶、多任務操作系統環境下的進程,但在進程和線程的運行及創建方式等方面,進程與Java線程具有明顯區別。
            
            Unix操作系統環境下,應用程序可以利用fork函數創建子進程,但子進程與該應用程序進程擁有獨立的地址空間、系統資源和代碼執行單元,并且進程的調度是由操作系統來完成的,使得在應用進程之間進行通信和線程協調相對復雜。而Java應用程序中的多線程則是共享同一應用系統資源的多個并行代碼執行體,線程之間的通信和協調方法相對簡單
            
            可以說:Java語言對應用程序多線程能力的支持增強了Java作為網絡程序設計語言的優勢,為實現分布式應用系統中多客戶端的并發訪問以及提高服務器的響應效率奠定堅實基礎。
          posted on 2005-06-24 09:43 蘋果 閱讀(505) 評論(1)  編輯  收藏 所屬分類: J2EE/JAVA學習

          評論

          # re: 【轉載至賽迪網】解析java的多線程機制 2005-06-24 09:50 蘋果
          一般來說,我們把正在計算機中執行的程序叫做"進程"(Process) ,而不將其
          稱為程序(Program)。所謂"線程"(Thread),是"進程"中某個單一順序的控制流。
          新興的操作系統,如Mac,Windows NT,Windows 95等,大多采用多線程的概念,把線
          程視為基本執行單位。線程也是Java中的相當重要的組成部分之一。

            甚至最簡單的Applet也是由多個線程來完成的。在Java中,任何一個Applet的
          paint()和update()方法都是由AWT(Abstract Window Toolkit)繪圖與事件處理線
          程調用的,而Applet 主要的里程碑方法——init(),start(),stop()和destory()
          ——是由執行該Applet的應用調用的。

            單線程的概念沒有什么新的地方,真正有趣的是在一個程序中同時使用多個線
          程來完成不同的任務。某些地方用輕量進程(Lightweig ht Process)來代替線程
          ,線程與真正進程的相似性在于它們都是單一順序控制流。然而線程被認為輕量是
          由于它運行于整個程序的上下文內,能使用整個程序共有的資源和程序環境。

            作為單一順序控制流,在運行的程序內線程必須擁有一些資源作為必要的開銷
          。例如,必須有執行堆棧和程序計數器。在線程內執行的代碼只在它的上下文中起
          作用,因此某些地方用"執行上下文"來代替"線程"。

            2.線程屬性

            為了正確有效地使用線程,必須理解線程的各個方面并了解Java 實時系統。
          必須知道如何提供線程體、線程的生命周期、實時系統如 何調度線程、線程組、
          什么是幽靈線程(Demo nThread)。

            (1)線程體
            所有的操作都發生在線程體中,在Java中線程體是從Thread類繼承的run()方
          法,或實現Runnable接口的類中的run()方法。當線程產生并初始化后,實時系統調
          用它的run()方法。run()方法內的代碼實現所產生線程的行為,它是線程的主要部
          分。

            (2)線程狀態
            附圖表示了線程在它的生命周期內的任何時刻所能處的狀態以及引起狀態改
          變的方法。這圖并不是完整的有限狀態圖,但基本概括了線程中比較感興趣和普遍
          的方面。以下討論有關線程生命周期以此為據。


            ●新線程態(New Thread)
            產生一個Thread對象就生成一個新線程。當線程處于"新線程"狀態時,僅僅是
          一個空線程對象,它還沒有分配到系統資源。因此只能啟動或終止它。任何其他操
          作都會引發異常。
            ●可運行態(Runnable)
            start()方法產生運行線程所必須的資源,調度線程執行,并且調用線程的run
          ()方法。在這時線程處于可運行態。該狀態不稱為運行態是因為這時的線程并不
          總是一直占用處理機。特別是對于只有一個處理機的PC而言,任何時刻只能有一個
          處于可運行態的線程占用處理 機。Java通過調度來實現多線程對處理機的共享。

            ●非運行態(Not Runnable)
            當以下事件發生時,線程進入非運行態。
            ①suspend()方法被調用;
            ②sleep()方法被調用;
            ③線程使用wait()來等待條件變量;
            ④線程處于I/O等待。
            ●死亡態(Dead)
            當run()方法返回,或別的線程調用stop()方法,線程進入死亡態 。通常Appl
          et使用它的stop()方法來終止它產生的所有線程。

            (3)線程優先級
            雖然我們說線程是并發運行的。然而事實常常并非如此。正如前面談到的,當
          系統中只有一個CPU時,以某種順序在單CPU情況下執行多線程被稱為調度(schedu
          ling)。Java采用的是一種簡單、固定的調度法,即固定優先級調度。這種算法是
          根據處于可運行態線程的相對優先級來實行調度。當線程產生時,它繼承原線程的
          優先級。在需要時可對優先級進行修改。在任何時刻,如果有多條線程等待運行,
          系統選擇優先級最高的可運行線程運行。只有當它停止、自動放棄、或由于某種
          原因成為非運行態低優先級的線程才能運行。如果兩個線程具有相同的優先級,它
          們將被交替地運行。
            Java實時系統的線程調度算法還是強制性的,在任何時刻,如果一個比其他線
          程優先級都高的線程的狀態變為可運行態,實時系統將選擇該線程來運行。

            (4)幽靈線程
            任何一個Java線程都能成為幽靈線程。它是作為運行于同一個進程內的對象
          和線程的服務提供者。例如,HotJava瀏覽器有一個稱為" 后臺圖片閱讀器"的幽靈
          線程,它為需要圖片的對象和線程從文件系統或網絡讀入圖片。
            幽靈線程是應用中典型的獨立線程。它為同一應用中的其他對象和線程提供
          服務。幽靈線程的run()方法一般都是無限循環,等待服務請求。

            (5)線程組
            每個Java線程都是某個線程組的成員。線程組提供一種機制,使得多個線程集
          于一個對象內,能對它們實行整體操作。譬如,你能用一個方法調用來啟動或掛起
          組內的所有線程。Java線程組由ThreadGroup類實現。
            當線程產生時,可以指定線程組或由實時系統將其放入某個缺省的線程組內。
          線程只能屬于一個線程組,并且當線程產生后不能改變它所屬的線程組。

            3.多線程程序

            對于多線程的好處這就不多說了。但是,它同樣也帶來了某些新的麻煩。只要
          在設計程序時特別小心留意,克服這些麻煩并不算太困難。

            (1)同步線程
            許多線程在執行中必須考慮與其他線程之間共享數據或協調執行狀態。這就
          需要同步機制。在Java中每個對象都有一把鎖與之對應。但Java不提供單獨的lo
          ck和unlock操作。它由高層的結構隱式實現, 來保證操作的對應。(然而,我們注
          意到Java虛擬機提供單獨的monito renter和monitorexit指令來實現lock和unlo
          ck操作。)
            synchronized語句計算一個對象引用,試圖對該對象完成鎖操作, 并且在完成
          鎖操作前停止處理。當鎖操作完成synchronized語句體得到執行。當語句體執行
          完畢(無論正常或異常),解鎖操作自動完成。作為面向對象的語言,synchronized
          經常與方法連用。一種比較好的辦法是,如果某個變量由一個線程賦值并由別的線
          程引用或賦值,那么所有對該變量的訪問都必須在某個synchromized語句或synch
          ronized方法內。
            現在假設一種情況:線程1與線程2都要訪問某個數據區,并且要求線程1的訪
          問先于線程2, 則這時僅用synchronized是不能解決問題的。這在Unix或Windows
          NT中可用Simaphore來實現。而Java并不提供。在Java中提供的是wait()和noti
          fy()機制。使用如下:
            synchronized method-1(…){ call by thread 1.
            ∥access data area;
            available=true;
            notify()
            }
            synchronized method-2(…){∥call by thread 2.
            while(!available)
            try{
            wait();∥wait for notify().
            }catch (Interrupted Exception e){
            }
            ∥access data area
            }
            其中available是類成員變量,置初值為false。
            如果在method-2中檢查available為假,則調用wait()。wait()的作用是使線
          程2進入非運行態,并且解鎖。在這種情況下,method-1可以被線程1調用。當執行
          notify()后。線程2由非運行態轉變為可運行態。當method-1調用返回后。線程2
          可重新對該對象加鎖,加鎖成功后執行wait()返回后的指令。這種機制也能適用于
          其他更復雜的情況。

            (2)死鎖
            如果程序中有幾個競爭資源的并發線程,那么保證均衡是很重要的。系統均衡
          是指每個線程在執行過程中都能充分訪問有限的資源。系統中沒有餓死和死鎖的
          線程。Java并不提供對死鎖的檢測機制。對大多數的Java程序員來說防止死鎖是
          一種較好的選擇。最簡單的防止死鎖的方法是對競爭的資源引入序號,如果一個線
          程需要幾個資源,那么它必須先得到小序號的資源,再申請大序號的資源。

            4.小結

            線程是Java中的重要內容,多線程是Java的一個特點。雖然Java的同步互斥不
          如某些系統那么豐富,但適當地使用它們也能收到滿意的效果。   回復  更多評論
            

          主站蜘蛛池模板: 武夷山市| 台中县| 鸡西市| 温泉县| 乌拉特前旗| 喀喇| 成都市| 固安县| 泉州市| 天柱县| 永川市| 丹东市| 西青区| 盖州市| 德惠市| 交口县| 丹阳市| 司法| 双江| 梧州市| 永丰县| 临邑县| 讷河市| 中超| 房山区| 东莞市| 大石桥市| 牟定县| 尤溪县| 噶尔县| 龙江县| 璧山县| 乐都县| 嵊州市| 石阡县| 会宁县| 苏尼特左旗| 枣庄市| 屏山县| 巴青县| 黄陵县|