多執行緒程式設計的基礎知識點

iteye_13777發表於2008-10-31
多執行緒程式設計一直是程式設計師比較頭痛和心虛的地方,因為執行緒執行順序的不可預知性和除錯時候的困難,讓不少人在面對多執行緒的情況下選擇了逃避,採用單執行緒的方式,其實只要我們對執行緒有了明確的認識,再加上Java內建的對多執行緒的天然支援,多執行緒程式設計不再是一道難以逾越的鴻溝。

「一」程式、執行緒、併發執行

關於程式、執行緒、併發執行的概念,我們先來看下面的一段話:

“一般來說,當執行一個應用程式的時候,就啟動了一個程式,當然有些會啟動多個程式。啟動程式的時候,作業系統會為程式分配資源,其中最主要的資源是記憶體空間,因為程式是在記憶體中執行的。

在程式中,有些程式流程塊是可以亂序執行的,並且這個程式碼塊可以同時被多次執行。實際上,這樣的程式碼塊就是執行緒體。執行緒是程式中亂序執行的程式碼流程。當多個執行緒同時執行的時候,這樣的執行模式成為併發執行。“

上面這段話引自51CTO網站“熔岩”的部落格一篇文章《Java多執行緒程式設計總結》,讀者可參考本文獲得更詳細的知識。

下面我以一個日常生活中簡單的例子來說明程式和執行緒之間的區別和聯絡:


這副圖是一個雙向多車道的道路圖,假如我們把整條道路看成是一個“程式”的話,那麼圖中由白色虛線分隔開來的各個車道就是程式中的各個“執行緒”了。

①這些執行緒(車道)共享了程式(道路)的公共資源(土地資源)。

②這些執行緒(車道)必須依賴於程式(道路),也就是說,執行緒不能脫離於程式而存在(就像離開了道路,車道也就沒有意義了)。

③這些執行緒(車道)之間可以併發執行(各個車道你走你的,我走我的),也可以互相同步(某些車道在交通燈亮時禁止繼續前行或轉彎,必須等待其它車道的車輛通行完畢)。

④這些執行緒(車道)之間依靠程式碼邏輯(交通燈)來控制執行,一旦程式碼邏輯控制有誤(死鎖,多個執行緒同時競爭唯一資源),那麼執行緒將陷入混亂,無序之中。

⑤這些執行緒(車道)之間誰先執行是未知的,只有線上程剛好被分配到CPU時間片(交通燈變化)的那一刻才能知道

「二」JVM與多執行緒

Java編寫的程式都執行在在Java虛擬機器(JVM)中,在JVM的內部,程式的多工是通過執行緒來實現的。

每用java命令啟動一個java應用程式,就會啟動一個JVM程式。在同一個JVM程式中,有且只有一個程式,就是它自己。在這個JVM環境中,所有程式程式碼的執行都是以執行緒來執行的。JVM找到程式程式的入口點main(),然後執行main()方法,這樣就產生了一個執行緒,這個執行緒稱之為主執行緒。當main方法結束後,主執行緒執行完成。JVM程式也隨即退出。

作業系統將程式執行緒進行管理,輪流(沒有固定的順序)分配每個程式很短的一段時間(不一定是均分),然後在每個程式內部,程式程式碼自己處理該程式內部執行緒的時間分配,多個執行緒之間相互的切換去執行,這個切換時間也是非常短的。

「三」Java語言對多執行緒的支援

Java語言對多執行緒的支援通過類Thread和介面Runnable來實現。這裡就不多說了。這裡重點強調兩個地方:

// 主執行緒其它程式碼段
ThreadClass subThread = new ThreadClass();
subThread.start();
// 主執行緒其它程式碼段
subThread.sleep(1000);
有人認為以下的程式碼在呼叫start()方法後,肯定是先啟動子執行緒,然後主執行緒繼續執行。在呼叫sleep()方法後CPU什麼都不做,就在那裡等待休眠的時間結束。實際上這種理解是錯誤的。因為:

①start()方法的呼叫後並不是立即執行多執行緒程式碼,而是使得該執行緒變為可執行態(Runnable),什麼時候執行是由作業系統決定的。

②Thread.sleep()方法呼叫目的是不讓當前執行緒獨自霸佔該程式所獲取的CPU資源,以留出一定時間給其他執行緒執行的機會(也就是靠內部自己協調)。

「四」執行緒的狀態切換

前面我們提到,由於執行緒何時執行是未知的,只有在CPU為執行緒分配到時間片時,執行緒才能真正執行。線上程執行的過程中,由可能會因為各種各樣的原因的原因而暫停(就像前面所舉的例子一樣:汽車只有在交通燈變綠的時候才能夠通行,而且在行駛的過程中可能會出現塞車,等待其它車輛通行或轉彎的狀況)。

這樣執行緒就有了“狀態”的概念,下面這副圖,是從《Java多執行緒程式設計總結》一文中摘錄出來的。很好的反映了執行緒在不同情況下的狀態變化


1、新建狀態(New):新建立了一個執行緒物件。

2、就緒狀態(Runnable):執行緒物件建立後,其他執行緒呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,變得可執行,等待獲取CPU的使用權。

3、執行狀態(Running):就緒狀態的執行緒獲取了CPU,執行程式程式碼。

4、阻塞狀態(Blocked):阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。阻塞的情況分三種:

①等待阻塞:執行的執行緒執行wait()方法,JVM會把該執行緒放入等待池中。

②同步阻塞:執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM把該執行緒放入鎖池③其他阻塞:執行的執行緒執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。

5、死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

「五」Java中執行緒的排程API

Java中關於執行緒排程的API最主要的有下面幾個:

①執行緒睡眠:Thread.sleep(long millis)方法

②執行緒等待:Object類中的wait()方法

③執行緒讓步:Thread.yield() 方法

④執行緒加入:join()方法

⑤執行緒喚醒:Object類中的notify()方法

關於這幾個方法的詳細應用,可以參考SUN的API,及《Java多執行緒程式設計總結》一文第七部分“執行緒的排程”。這裡我重點總結一下這幾個方法的區別和使用:

備註:sleep方法與wait方法的區別:①sleep方法是靜態方法,wait方法是非靜態方法。

②sleep方法在時間到後會自己“醒來”,但wait不能,必須由其它執行緒通過notify(All)方法讓它“醒來”

③sleep方法通常用在不需要等待資源情況下的阻塞,像等待執行緒、資料庫連線的情況一般用wait

備註:sleep/wait與yeld方法的區別:①呼叫sleep或wait方法後,執行緒即進入block狀態,而呼叫yeld方法後,執行緒進入runnable狀態

備註:wait與join方法的區別:①wait方法體現了執行緒之間的互斥關係,而join方法體現了執行緒之間的同步關係②wait方法必須由其它執行緒來解鎖,而join方法不需要,只要被等待執行緒執行完畢,當前執行緒自動變為就緒③join方法的一個用途就是讓子執行緒在完成業務邏輯執行之前,主執行緒一直等待直到所有子執行緒執行完畢。

相關文章