標籤:
線程初識
沒有線程編程的程式好比一個人一隻手幹活,而多線程的程式就好比一個人多隻手幹活.
進程是系統資源分派的獨立單位,而線程是可調度啟動並執行獨立單位,一個進程可以擁有多個線程,線程是進程並行完成的多個任務.
進程、線程、並發執行
首先我們先來認識一下進程、線程、並發執行的概念:
一般來說,當運行一個應用程式的時候,就啟動了一個進程,當然有些會啟動多個進程。啟動進程的時候,作業系統會為進程分配資源,其中最主要的資源是記憶體空間,因為程式是在記憶體中啟動並執行。
在進程中,有些程式流程塊是可以亂序執行的,並且這個代碼塊可以同時被多次執行。實際上,這樣的代碼塊就是線程體。線程是進程中亂序執行的代碼流程。當多個線程同時啟動並執行時候,這樣的執行模式成為並發執行。
下面我以一個日常生活中簡單的例子來說明進程和線程之間的區別和聯絡:
這副圖是一個雙向多車道的道路圖,假如我們把整條道路看成是一個“進程”的話,那麼圖中由白色虛線分隔開來的各個車道就是進程中的各個“線程”了。
- 這些線程(車道)共用了進程(道路)的公用資源(土地資源)。
- 這些線程(車道)必須依賴於進程(道路),也就是說,線程不能脫離於進程而存在(就像離開了道路,車道也就沒有意義了)。
- 這些線程(車道)之間可以並發執行(各個車道你走你的,我走我的),也可以互相同步(某些車道在交通燈亮時禁止繼續前行或轉彎,必須等待其它車道的車輛通行完畢)。
- 這些線程(車道)之間依靠代碼邏輯(交通燈)來控制運行,一旦代碼邏輯控制有誤(死結,多個線程同時競爭唯一資源),那麼線程將陷入混亂,無序之中。
- 這些線程(車道)之間誰先運行是未知的,只有線上程剛好被分配到CPU時間片(交通燈變化)的那一刻才能知道。
並發和並行的區別就是一個處理器同時處理多個任務和多個處理器或者是多核的處理器同時處理多個不同的任務.
並發是邏輯上的同時發生(simultaneous),並行者是物理上的同時發生.
並發性 (concurrency),又稱共行性,是指能處理多個同時性活動的能力,並發事件之間不一定要同一時刻發生.
並行性 (parallelism) 是指同時發生的兩個並發事件,具有並發的含義,而並發則不一定並行.
也就是說,多進程之間並發執行,而多線程編程就是並存執行的了.
來個比喻:並發和並行的區別就是一個人同時吃三個饅頭和三個人同時吃三個饅頭.
這樣,對於進程與線程,並發和並行的區別,您應該瞭解了吧.反正我是明白了!
Java中的線程
在Java中,“線程”指
兩件不同的事情:
1、java.lang.Thread類的一個執行個體;2、線程的執行。 使用java.lang.Thread類或者java.lang.Runnable介面編寫代碼來定義、執行個體化和啟動新線程。 一個Thread類執行個體只是一個對象,像Java中的任何其他對象一樣,具有變數和方法,生死於堆上。 Java中,每個線程都有一個調用棧,即使不在程式中建立任何新的線程,線程也在後台運行著。 一個Java應用總是從main()方法開始運行,mian()方法運行在一個線程內,它被稱為主線程。 一旦建立一個新的線程,就產生一個新的調用棧。 線程總體分兩類:使用者線程和守候線程。
當所有使用者線程執行完畢的時候,JVM自動關閉。但是守候線程卻不獨立於JVM,守候線程一般是由作業系統或者使用者自己建立的。(此處仍有疑問!)
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為線程分配到時間片時,線程才能真正執行。線上程執行的過程中,由可能會因為各種各樣的原因而暫停(就像前面所舉的例子一樣:汽車只有在交通燈變綠的時候才能夠通行,而且在行駛的過程中可能會出現塞車,等待其它車輛通行或轉彎的狀況)。
這樣線程就有了“狀態”的概念,下面這副圖很好的反映了線程在不同情況下的狀態變化。
- 建立狀態(New):新建立了一個線程對象。
- 就緒狀態(Runnable):線程對象建立後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待擷取CPU的使用權。
- 運行狀態(Running):就緒狀態的線程擷取了CPU,執行程式代碼。
- 阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
- 等待阻塞:啟動並執行線程執行wait()方法,JVM會把該線程放入等待池中。
- 同步阻塞:啟動並執行線程在擷取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM把該線程放入鎖。
- 其他阻塞:啟動並執行線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態逾時、join()等待線程終止或者逾時、或者I/O處理完畢時,線程重新轉入就緒狀態。
- 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
Java中線程的調度API
Java中關於線程調度的API最主要的有下面幾個:
- 線程睡眠:Thread.sleep(long millis)方法
- 線程等待:Object類中的wait()方法
- 線程讓步:Thread.yield() 方法
- 線程加入:join()方法
- 線程喚醒:Object類中的notify()方法
關於這幾個方法的詳細應用,可以參考SUN的API。這裡我重點總結一下這幾個方法的區別和使用。
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方法的一個用途就是讓子線程在完成商務邏輯執行之前,主線程一直等待直到所有子線程執行完畢。
通過上面的介紹相信同學們對java裡面的多線程已經有了基本的瞭解和認識。其實多線程編程並沒有大家想象中的那麼難,只要在實際的學習,工作當中不斷的加以練習和使用,相信大家很快就能掌握其中的奧妙,從而編寫出賞心悅目的java程式。
本文借鑒資料:
http://express.ruanko.com/ruanko-express_6/webpage/tech4.html
http://lavasoft.blog.51cto.com/62575/27069/
瘋狂Java學習筆記(62)------------線程初識