第十二章 多線程
當計算機處於DOS時代時,程序幾乎是沒有界面的,而且由於計算機運行速度等原因,那個時代的計算機只能啟動一個程序,只有當該程序退出以後才可以執行其它的程序。但是隨著計算機性能的提高,以及軟件的豐富,如果計算機還只能同時執行一個程序的話,那麼計算機恐怕是很多人都不能接受的。
這種在任何一個時間點,可以有多個程序同時執行,或者有多個程序邏輯同時執行的能力,成為並發執行。
現在計算機早已進入到並發執行的時代,對於程序編程來說,進行並發執行的程序編寫也就被稱作並發編程,在Java語言中,同一個程序內部的並發處理由線程這個概念來實現。
12.1 多線程簡介
從小時候開始,老師就教育大家——“一心不可二用”,這是指做一件事情的時候一定要專注,不能夠分心。但是在程序編程的領域卻早已經需要做到“一心二用”甚至“一心多用”了。下面來看一下線程的概念吧!
12.1.1 進程和線程
在介紹線程的概念以前,首先介紹一下進程的概念。
進程(Process)指操作系統中一個獨立運行的程序。例如在計算機中,同時運行著QQ、Word、MSN等,那麼QQ程序是一個進程,MSN程序也是一個進程。在Windows操作系統中的任務管理器中,就可以清晰的看到當前操作系統中正在運行的進程信息。
進程,也稱任務,所以支持多個進程同時執行的操作系統就被稱作多進程操作系統或多任務操作系統,現在主流的操作系統都屬於這種類型。在操作系統中,每個進程擁有獨立的內存空間等系統資源,進程和進程之間的系統資源不互用,所以進程之間的通信比較麻煩。通過在操作系統上同時運行多個進程,可以充分發揮計算機的硬件能力,更方便用戶使用,也使得各種各樣的程序大量出現。
對於只有一個CPU的計算機來說,是如何實現同時執行多個進程的呢?其實CPU采用的原理就是分時執行,每個進程處於操作系統的進程隊列中。然後每個進程依次獲得一個時間片進入CPU進行執行,在該時間片執行完成以後,該進程保存自身狀態,退出CPU,然後其它的進程進入CPU繼續執行。由於時間片的時間很短,例如Windows操作系統的時間片是20ms,所以在計算機用戶看來程序就是同時執行的,而實際的執行方式是穿插依次執行的。而對於多CPU的計算機來說,只是排隊的隊列增加了幾個而已,每個隊列的實現方式和上面的介紹類似。
但是進程的概念相對比較大,而且需要成為一個獨立的程序,這樣對於編程來說比較麻煩,所以在程序開發中設計了另外一個概念——線程。
線程(Thread)指同一個程序(進程)內部每個單獨執行的流程。在前面的程序中每個程序內部都只包含一個系統流程,該流程從main方法開始,隨著方法的調用進入到每個方法的內部,在方法調用完成以後返回到調用的位置,直到main方法結束以後則該流程結束,這個流程就是前面程序中的系統線程。Java語言對於線程的概念提供了良好的支持,在編程中實際使用線程也顯得比其它語言要簡單一些。
而在實際實現時,Java語言支持在一個程序內部同時執行多個流程,其中每個單獨的流程就是一個線程。例如在QQ程序中,系統的線程負責響應用戶的按鍵操作,在後台可以啟動網絡通訊的線程執行數據的發送和接收,這樣兩個流程之間同時執行,並協調進行工作。而在服務器端程序中,每個和服務器進行通訊的客戶端,在服務器端都會啟動一個對應的線程進行通訊,這樣每個客戶端才顯得同時和服務器端進行通訊。
在很多地方,線程被看作是一種“輕量級進程”,因為使用線程和進程的改變比較類似,而且使用線程時對於系統資源,如內存、CPU等,的占用要比進程小很多,也就是有更小的系統開銷。另外,同一個程序中的線程之間變量是共享的,線程之間的數據交換要比進程之間的數據交換簡單一些。
總之,無論是進程的概念還是線程的概念,都使編程從串行編程(依次執行)進入到並行編程(同時執行)的領域,而在CPU內部實現的原理都是按照時間片進行切換。
12.1.2 多線程優勢
線程的概念增加了編程的難度,也增加了程序的復雜度,但是該概念還是在程序內部大量進行使用,這主要因為多線程程序的優勢。
多線程程序主要的優勢有兩個:
1、 提高界面程序響應速度
通過使用線程,可以將需要大量時間完成的流程在後台完成,例如現在常見的網絡程序,在進行網絡通訊時都需要使用單獨的流程進行,也就是啟動一個單獨的線程進行,這樣不會阻塞系統線程的執行,也就是不會阻塞對於界面的操作。另外,如果需要大量操作數據或進行數據變換的程序,也需要在後台啟動單獨的線程來提高前台界面的響應速度。
通過將程序邏輯獨立成一個單獨的線程,使得控制界面的系統線程和邏輯線程同時執行,避免了邏輯操作需要大量的時間阻塞系統的線程執行,從而大幅度提高界面程序的響應速度。
2、 充分利用系統資源
通過在一個程序內部同時執行多個流程,可以充分利用CPU等系統資源,從而最大限度的發揮硬件的吸能。就像一個人同時承擔多份工作一樣,這樣可以使這個人的時間獲得比較充分的使用。
當然,多線程程序也有一些不足,例如當程序中的線程數量比較多時,系統將花費大量的時間進行線程的切換,這反而會降低程序的執行效率。
但是,相對於優勢來說,劣勢還是很有限的,所以在現在的項目開發中,多線程編程技術獲得了廣泛的使用。
12.1.3 線程生命周期
線程作為一個全新的概念,主要由系統進行管理,但是熟悉線程概念的各個階段,是控制線程程序執行的基礎,和以後學習的其它Java技術類似,線程在程序中從出現到消亡的各個階段,在程序中統稱為線程的生命周期。
在Java語言中線程的概念由java.lang.Thread類實現,在該類中封裝線程的概念,並且將線程控制的相關方法包含在該類的內部。後續介紹中如果沒有特別說明,則提到的方法均是Thread類內部的方法。
線程的生命周期中包含如下階段:
1、 新建狀態(New)
該狀態指線程已經初始化完成,但是還沒有啟動。具體點說,也就是線程對象已經創建,准備工作已經完成。
2、 運行狀態(Run)
運行狀態是指線程的正常執行狀態,處於該狀態的線程在CPU內部執行程序,也就是線程正常運行時的狀態。
3、 阻塞狀態(Block)
阻塞狀態指線程處於執行狀態,但是由於沒有獲得CPU的執行時間,而處於CPU外部等待線程執行的狀態。
4、 死亡狀態(Dead)
死亡狀態指線程執行結束,釋放線程占用的系統資源,結束線程執行的狀態。
在實際使用線程時,首先需要創建一個線程對象,在線程對象創建完成以後,該線程就處於新建狀態了,在新建狀態下的線程,已經初始化完成,但是還沒有啟動,也就是不會獲得CPU的執行時間。在新建狀態下,一般可以通過調用線程對象中的start方法,使線程進入到運行狀態,start方法不阻塞程序的執行,在調用完成以後立刻就返回了。一旦線程進入運行狀態,則開始排隊進入CPU執行,根據系統的調度,線程就在運行狀態和阻塞狀態之間進行切換,這就是線程的執行狀態。當線程執行完成或需要結束該流程時,則需要將線程切換到死亡狀態,釋放線程占用的資源,結束線程的執行。
另外在線程執行的過程中也可以根據需要調用Thread類中對應的方法改變線程的狀態。例如使用線程對象的interrupt中斷線程的執行,使線程進入到死亡狀態;使用yield方法使當前正在執行的線程從運行狀態切換到阻塞狀態。
而具體線程編程的實現方式、線程的控制以及線程編程時需要注意的問題,則將在下面進行詳細的介紹。