熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

解析Java的多線程機制

2022-06-13   來源: Java高級技術 

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

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

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

  線程間的同步
  
  
Java應用程序的多個線程共享同一進程的數據資源多個用戶線程在並發運行過程中可能同時訪問具有敏感性的內容在Java中定義了線程同步的概念實現對共享資源的一致性維護下面以筆者最近開發的移動通信計費系統中線程間同步控制方法說明Java語言中多線程同步方式的實現過程
  
  在沒有多線程同步控制策略條件下的客戶賬戶類定義框架代碼如下所示
  
  public class RegisterAccount
  {
   float fBalance;
   file://客戶繳費方法
   public void deposit(float fFees){ fBalance += fFees; }
   file://通話計費方法
   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將線程的優先級分為個等級分別用之間的數字表示數字越大表明線程的級別越高相應地在Thread類中定義了表示線程最低最高和普通優先級的成員變量MIN_PRIORITYMAX_PRIORITY和NORMAL_PRIORITY代表的優先級等級分別為當一個線程對象被創建時其默認的線程優先級是
  
  為了控制線程的運行策略Java定義了線程調度器來監控系統中處於就緒狀態的所有線程線程調度器按照線程的優先級決定那個線程投入處理器運行在多個線程處於就緒狀態的條件下具有高優先級的線程會在低優先級線程之前得到執行線程調度器同樣采用搶占式策略來調度線程執行即當前線程執行過程中有較高優先級的線程進入就緒狀態則高優先級的線程立即被調度執行具有相同優先級的所有線程采用輪轉的方式來共同分配CPU時間片
  
  在應用程序中設置線程優先級的方法很簡單在創建線程對象之後可以調用線程對象的setPriority方法改變該線程的運行優先級同樣可以調用getPriority方法獲取當前線程的優先級
  
  在Java中比較特殊的線程是被稱為守護(Daemon)線程的低級別線程這個線程具有最低的優先級用於為系統中的其它對象和線程提供服務將一個用戶線程設置為守護線程的方式是在線程對象創建之前調用線程對象的setDaemon方法典型的守護線程例子是JVM中的系統資源自動回收線程它始終在低級別的狀態中運行用於實時監控和管理系統中的可回收資源
  
  線程分組管理
  
  Java定義了在多線程運行系統中的線程組(ThreadGroup)對象用於實現按照特定功能對線程進行集中式分組管理用戶創建的每個線程均屬於某線程組這個線程組可以在線程創建時指定也可以不指定線程組以使該線程處於默認的線程組之中但是一旦線程加入某線程組該線程就一直存在於該線程組中直至線程死亡不能在中途改變線程所屬的線程組
  
  當Java的Application應用程序運行時JVM創建名稱為main的線程組除非單獨指定在該應用程序中創建的線程均屬於main線程組在main線程組中可以創建其它名稱的線程組並將其它線程加入到該線程組中依此類推構成線程和線程組之間的樹型管理和繼承關系
  
  與線程類似可以針對線程組對象進行線程組的調度狀態管理以及優先級設置等在對線程組進行管理過程中加入到某線程組中的所有線程均被看作統一的對象

  小結

  本文針對Java平台中線程的性質和應用程序的多線程策略進行了分析和講解
  
  與其它操作系統環境不同Java運行環境中的線程類似於多用戶多任務操作系統環境下的進程但在進程和線程的運行及創建方式等方面進程與Java線程具有明顯區別
  
  Unix操作系統環境下應用程序可以利用fork函數創建子進程但子進程與該應用程序進程擁有獨立的地址空間系統資源和代碼執行單元並且進程的調度是由操作系統來完成的使得在應用進程之間進行通信和線程協調相對復雜而Java應用程序中的多線程則是共享同一應用系統資源的多個並行代碼執行體線程之間的通信和協調方法相對簡單
  
  可以說Java語言對應用程序多線程能力的支持增強了Java作為網絡程序設計語言的優勢為實現分布式應用系統中多客戶端的並發訪問以及提高服務器的響應效率奠定堅實基礎


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27665.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.