更新時間:12/24/2018
前言
以前電腦的 CPU 只有單一實體核心,而現在電腦的 CPU 都是實體多顆核心,能夠讓作業系統同時處理許多程序(Process)與執行緒(Thread),使得應用程式充分利用 CPU 資源。此篇文章是 Java (多)執行緒的一個入門,其實 Java 裡頭的執行緒完全可以寫成一本書了!這邊就是如何在 Java 中簡單使用(多)執行緒,並介紹(多)執行緒產生的後遺症。
執行緒是作業系統能夠進行運算排程的最小單位。它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以並行多個執行緒,每條執行緒並列執行不同的任務。
為什麼使用執行緒?
在 Java 程序中使用執行緒有許多原因。如果您使用 Swing、servlet、RMI 或 Enterprise JavaBeans(EJB)技術,您也許沒有意識到您已經在使用執行緒了。使用執行緒的一些原因是它們可以幫助:
使 UI 響應更快
事件驅動的 UI 工具箱(如 AWT 和 Swing)有一個事件執行緒,它處理 UI 事件,如按下鍵盤或點擊滑鼠。
AWT 和 Swing 程序把事件偵聽器與 UI 物件連接。當特定事件(如單擊了某個按鈕)發生時,這些偵聽器會得到通知。事件偵聽器是在 AWT 事件執行緒中調用的。
如果事件偵聽器要執行持續很久的任務,如檢查一個大文檔中的拼寫,事件執行緒將忙於運行拼寫檢查器,所以在完成事件偵聽器之前,就不能處理額外的 UI 事件。這就會使程序看來似乎停滯了,讓用戶不知所措。
要避免使UI 延遲響應,事件偵聽器應該把較長的任務放到另一個執行緒中,這樣 AWT 執行緒在任務的執行過程中就可以繼續處理 UI 事件(包括取消正在執行的長時間運行任務的請求)。
利用多處理器(MultiProcessor, MP)系統
多處理器(MP)系統比過去更普及了。以前只能在大型數據中心和科學計算設施中才能找到它們。現在許多低端服務器系統,甚至是一些個人主機或行動裝置系統都有多個處理器。
現代操作系統,包括 Linux、Solaris 和 Windows Server/7/10,都可以利用多個處理器並調度執行緒在任何可用的處理器上執行。
調度的基本單位通常是執行緒;如果某個程序只有一個活動的執行緒,它一次只能在一個處理器上運行。如果某個程序有多個活動執行緒,那麼可以同時調度多個執行緒。在精心設計的程序中,使用多個執行緒可以提高程序吞吐量和性能。
簡化建模
在某些情況下,使用執行緒可以使程序編寫和維護起來更簡單。考慮一個仿真應用程序,您要在其中模擬多個實體之間的交互作用。給每個實體一個自己的執行緒可以使許多仿真和對應用程序的建模大大簡化。
另一個適合使用單獨執行緒來簡化程序的示例是在一個應用程序有多個獨立的事件驅動組件的時候。例如,一個應用程序可能有這樣一個組件,該組件在某個事件之後用秒數倒數計時,並更新螢幕顯示。與其讓一個主循環定期檢查時間並更新顯示,不如讓一個執行緒什麼也不做,一直休眠,直到某一段時間後,更新螢幕上的計數器,這樣更簡單,而且不容易出錯。這樣,主執行緒就根本無需擔心計時器。
執行異步(Asynchronous)或後台處理
服務器應用程序從遠程來源,如網路插座(Network socket, 網路埠)獲取輸入。當讀取 Network socket 時,如果當前沒有可用數據,那麼對 SocketInputStream.read() 的調用將會阻塞,直到有可用數據為止。
如果單執行緒程序要讀取 Network socket,而 Network socket 另一端的實體並未發送任何數據,那麼該程序只會永遠等待,而不執行其它處理。相反,程序可以輪詢 Network socket,查看是否有可用數據,但通常不會使用這種做法,因為會影響性能。
但是,如果您創建了一個執行緒來讀取 Network socket,那麼當這個執行緒等待 Network socket 中的輸入時,主執行緒就可以執行其它任務。您甚至可以創建多個執行緒,這樣就可以同時讀取多個 Network socket。這樣,當有可用數據時,您會迅速得到通知(因為正在等待的執行緒被喚醒),而不必經常輪詢以檢查是否有可用數據。使用執行緒等待 Network socket 的代碼也比輪詢更簡單、更不易出錯。