Java的多線程機制

程序、進程和線程:
 程序是一段靜態的代碼,它是應用程序執行的藍本。進程是程序的一次動態執行過程,它對應了從代碼加載、執行至執行完畢的一個完整過程,這個過程也是進程本身從產生、發展至消亡的過程。線程是比進程更小的單位,一個進程執行過程中可以產生多個線程,每個線程有自身的產生、存在和消亡的過程,也是一個動態的概念。每個進程都有一段專用的內存區域,而線程間可以共享相同的內存區域(包括代碼和數據),並利用這些共享單元來實現數據交換、實時通信與必要的同步操作。
 
 每個Java程序都有一個默認的主線程。Java程序總是從主類的main方法開始執行。當JVM加載代碼,發現main方法後就啓動一個線程,這個線程就稱作"主線程",該線程負責執行main方法。在main方法中再創建的線程就是其他線程。

 如果main方法中沒有創建其他線程,那麼當main方法返回時JVM就會結束Java應用程序。但如果main方法中創建了其他線程,那麼JVM就要在主線程和其他線程之間輪流切換,保證每個線程都有機會使用CPU資源,main方法返回(主線程結束)JVM也不會結束,要一直等到該程序所有線程全部結束才結束Java程序(另外一種情況是:程序中調用了Runtime類的exit方法,並且安全管理器允許退出操作發生。這時JVM也會結束該程序)。
 

 線程的狀態與生命週期:
 Java使用java.lang.Thread類及其子類的對象表示線程,新建的線程在它的一個完整生命週期中通常要經歷如下四種狀態:(新建、運行、(中斷/掛起/阻塞)、消亡)
 1、新建:
 當一個Thread類或其子類對象被聲明並創建,新生的線程對象就處於新建狀態(此時它已經有了內存空間和其他資源)。
 
 2、運行:
 線程已經創建就具備了運行的條件,一旦輪到它來享用CPU資源時,即JVM將CPU使用權切換給該線程時,此線程就可以脫離創建它的主線程獨立開始自己的生命週期了。
 線程創建後僅僅是佔有了內存資源,在JVM管理的線程中還沒有這個線程,此線程必須調用start()方法(從父類繼承)通知JVM,這樣JVM就知道又有一個新的線程排隊等候切換。

 當JVM將CPU使用權切換給線程時,如果線程是Thread的子類創建的,該類中的run()方法立刻執行(run()方法中規定了該線程的具體使命)。Thread類中的run()方法沒有具體內容,程序要在Thread類的子類中重寫run()方法覆蓋父類該方法。(注意:在線程沒有結束run()方法前,不要再調用start方法,否則將發生ILLegalThreadStateException異常)
 
 3、掛起:
 線程掛起的原因有一下四種:
 (1)、JVM將CPU資源從當前線程切換給其他線程,使本線程讓出CPU的使用權,並處於掛起狀態。
 (2)、線程使用CPU資源期間,執行了sleep(int millsecond)方法,使當前線程進入休眠狀態。sleep(int millsecond)方法是Thread類中的一個類方法,線程執行該方法就立刻讓出CPU使用權,進入掛起狀態。經過參數millsecond指定的毫秒數之後,該線程就重新進到線程隊列中排隊等待CPU資源,然後從中斷處繼續運行。
 (3)、線程使用CPU資源期間,執行了wait()方法,使得當前線程進入等待狀態。等待狀態的線程不會主動進入線程隊列等待CPU資源,必須由其他線程調用notify()方法通知它,才能讓該線程從新進入到線程隊列中排隊等待CPU資源,以便從中斷處繼續運行。
 (4)、線程使用CPU資源期間,執行某個操作進入阻塞狀態,如執行讀/寫操作引起阻塞。進入阻塞狀態時線程不能進入線程隊列,只有引起阻塞的原因消除時,線程才能進入到線程隊列排隊等待CPU資源,以便從中斷處繼續運行。
 
 4、死亡:
 死亡狀態就是線程釋放了實體,即釋放了分配給線程對象的內存。線程死亡的原因有兩個:
 (1)、正常運行的線程完成了它的全部工作,即執行完run()方法中的全部語句,結束了run()方法。
 (2)、線程被提前強制性終止,即強制run()方法結束。
 

 線程調度與優先級:
 JVM的線程調度器負責管理線程,調度器把線程的優先級分爲10個級別,分別用Thread類中的類常量表示。每個Java線程的優先級都在常數1-10之間。Thread類優先級常量有三個:
  static int MIN_PRIORITY  //1
  static int NORM_PRIORITY //5
  static int MAX_PRIORITY  //10
 如果沒有明確設置,默認線程優先級爲常數5即Thread.NORM_PRIORITY。

 線程優先級可以用setPriority(int grade)方法調整,如果參數grade不在1-10範圍內,那麼setPriority產生一個IllegalArgumenException異常。用getPriority()方法返回線程優先級。(注意:有些操作系統只能識別3個級別:1、5、10)
 
 Java調度器的任務是使優先級高的線程能始終運行,一旦時間片有空閒,則使具有同等優先級的線程以輪流的方式順序使用時間片。只有當高級別的線程死亡時(除非用sleep(int millsecond)或wait()方法讓出CPU資源),低級別線程纔有機會獲得CPU資源。
 
 實際編程時,不提倡使用線程的優先級來保證算法的正確執行。要編寫正確、跨平臺的多線程代碼,必須假設線程在任何時刻都有可能被剝奪CPU資源的使用權。
 

 Java中實現多線程有兩種方法:
 1、繼承Thread類,覆蓋run()方法:使用Thread子類創建線程的優點是可以在子類中增加新的成員變量或方法,使線程具有某種屬性或功能。但Java不支持多繼承,Thread類的子類不能再擴展其他的類。
 2、實現Runnable接口:用Thread類直接創建線程對象,使用構造函數Thread(Runnable target)(參數target是一個Runnable接口),創建線程對象時必須向構造方法參數傳遞一個實現Runnable接口類的實例,該實例對象稱作所創線程的目標對象。當線程調用start()方法,一旦輪到它使用CPU資源,目標對象自動調用接口中的run()方法(接口回調)。

 線程間可以共享相同的內存單元(包括代碼和數據),並利用這些共享單元來實現數據交換、實時通信與必要的同步操作。對於Thread(Runnable target)創建的使用同一目標對象的線程,可以共享該目標對象的成員變量和方法。
 另外,創建目標對象類在必要時還可以是某個特定類的子類,因此,使用Runnable接口比使用Thread的子類更具有靈活性。
 (注意:具有相同目標對象的線程,run()方法中的局部變量相互獨立,互不干擾)
 
 在線程中啓動其他線程,當線程調用start()方法啓動,使之從新建態進入就緒隊列,一旦得到CPU資源就脫離創建它的主線程,開始自己的生命週期。
 

 線程的常用方法:
 start():線程調用該方法將啓動線程,從新建態進入就緒隊列,一旦享用CPU資源就可以脫離創建它的線程,獨立開始自己的生命週期。

 run():Thread類的run()方法與Runnable接口中的run()方法功能和作用相同,都用來定義線程對象被調度後所進行的操作,都是系統自動調用而用戶不得引用的方法。run()方法執行完畢,線程就成死亡狀態,即線程釋放了分配給它的內存(死亡態線程不能再調用start()方法)。在線程沒有結束run()方法前,不能讓線程再調用start()方法,否則將發生IllegalThreadStateException異常。

 sleep(int millsecond):有時,優先級高的線程需要優先級低的線程做一些工作來配合它,此時爲讓優先級高的線程讓出CPU資源,使得優先級低的線程有機會運行,可以使用sleep(int millsecond)方法。線程在休眠時被打斷,JVM就拋出InterruptedException異常。因此,必須在try-catch語句塊中調用sleep方法。

 isAlive():當線程調用start()方法並佔有CPU資源後該線程的run()方法開始運行,在run()方法沒有結束之前調用isAlive()返回true,當線程處於新建態或死亡態時調用isAlive()返回false。
 注意:一個已經運行的線程在沒有進入死亡態時,不要再給它分配實體,由於線程只能引用最後分配的實體,先前的實體就成爲了"垃圾",並且不能被垃圾回收機制收集。

 currentThread():是Thread類的類方法,可以用類名調用,返回當前正在使用CPU資源的線程。

 interrupt():當線程調用sleep()方法處於休眠狀態,一個佔有CPU資源的線程可以讓休眠的線程調用interrupt()方法"吵醒"自己,即導致線程發生IllegalThreadStateException異常,從而結束休眠,重新排隊等待CPU資源。
 

 GUI線程:JVM在運行包含圖形界面應用程序時,會自動啓動更多線程,其中有兩個重要的線程:AWT-EventQueue和AWT-Windows。AWT-EventQueue線程負責處理GUI事件,AWT-Windows線程負責將窗體或組件繪製到桌面。
 

 線程同步:(用synchronized修飾某個方法,該方法修改需要同步的變量;或用volatile修飾基本變量)
 當兩個或多個線程同時訪問一個變量,並且一個線程需要修改這個變量時,應對這樣的問題進行處理,否則可能發生混亂。

 要處理線程同步,可以把修改數據的方法用關鍵字synchronized修飾。一個方法使用synchronized修飾,當一個線程A使用這個方法時,其他線程想使用該方法時就必須等待,直到線程A使用完該方法。所謂同步就是多個線程都需要使用一個synchronized修飾的方法。
 
 volatile比同步簡單,只適合於控制對基本變量(整數、布爾變量等)的單個實例的訪問。java中的volatile關鍵字與C++中一樣,用volatile修飾的變量在讀寫操作時不會進行優化(取cache裏的值以提高io速度),而是直接對主存進行操作,這表示所有線程在任何時候看到的volatile變量值都相同。
 

 在同步方法中使用wait()、notify()、notifyAll()方法:
 當一個線程使用的同步方法中用到某個變量,而此變量又需要其他線程修改後才能符合本線程需要,那麼可以在同步方法中使用wait()方法。中斷方法的執行,使本線程等待,暫時讓出CPU資源,並允許其他線程使用這個同步方法。其他線程如果在使用這個同步方法時不需要等待,那麼它使用完這個同步方法時應當用notifyAll()方法通知所有由於使用這個同步方法而處於等待的線程結束等待。曾中斷的線程就會從中斷處繼續執行,並遵循"先中斷先繼續"的原則。如果用的notify()方法,那麼只是通知等待中的線程中某一個結束等待。
 

 計時器線程Timer:(Timer還有很多高級操作,詳細見JDK,這裏做個概述)
 java.swing.Timer類用於週期性地執行某些操作。有兩個常用構造函數
 public Timer(int delay, ActionListener listener):參數listener是計時器的監視器,計時器發生振鈴的事件是ActionEvent類型事件,當振鈴事件發生,監視器會監視到這個事件並回調ActionListener接口中的actionPerformed(ActionEvent e)方法。

 public Timer(int delay):使用該構造方法,計時器要再調用addActionListener(ActionListener listener)方法獲得監視器。
 
 如果想讓計時器只震動一次,可以讓計時器調用setRepeats(boolean b)方法,參數b取false即可。
 計時器還可以調用setInitialDelay(int delay)方法設置首次振鈴的延時,如果沒有設置首次振鈴默認延時爲構造函數中的參數delay。
 還可以調用getDelay()和setDelay(int delay)獲取和設置延時。
 計時器創建後調用start()啓動,調用stop()停止,即掛起,調用restart()重新啓動計時器,即恢復線程。
 

 線程聯合:
 一個線程A在佔有CPU資源期間,可以讓其他線程調用join()方法和本線程聯合,如:
  B.join();
 此時稱A在運行期間聯合了B。這時A線程立刻終端執行,一直等到它聯合的線程B執行完畢,A線程再重新排隊等待CPU資源。但如果A準備聯合的線程B已經結束,則B.join()不會產生任何效果。
 
 
 守護線程:
 線程默認是非守護線程,非守護線程也稱用戶線程,一個線程調用setDaemon(boolean on)方法可以將自己設置成一個守護(Daemon)線程,如:
  thread.setDaemon(true);
 一個線程必須在自己運行之前設置自己是否是守護線程。守護線程是當程序中所有用戶線程都已結束運行時即使守護線程的run()方法還有需要執行的語句,守護線程也會立刻結束運行。因此守護線程用於做一些不是很嚴格的工作,當線程隨時結束時不會產生什麼不良後果。

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/lj70024/archive/2010/04/06/5455790.aspx

發佈了210 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章