 多線程編程一直是學員們比較頭痛和心虛的地方,因為線程執行順序的不可預知性和調試時候的困難,讓不少人在面對多線程的情況下選擇了逃避,采用單線程的方式,其實只要我們對線程有了明確的認識,再加上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為線程分配到時間片時,線程才能真正執行。在線程執行的過程中,由可能會因為各種各樣的原因而暫停(就像前面所舉的例子一樣:汽車只有在交通燈變綠的時候才能夠通行,而且在行駛的過程中可能會出現塞車,等待其它車輛通行或轉彎的狀況)。 這樣線程就有了“狀態”的概念,下面這副圖很好的反映了線程在不同情況下的狀態變化。 
- 新建狀態(New):新創建了一個線程對象。
- 就緒狀態(Runnable):線程對象創建后,其他線程調用了該對象的start()方法。該狀態的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權。
- 運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
- 阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
- 等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
- 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM把該線程放入鎖。
- 其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
- 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。
Java中線程的調度APIJava中關于線程調度的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程序。
|