許多情況下,在一個程式中使用多線程是有益處的。下面是一些深度的觀察,為什麼是有好處的。
與使用者的更好互動(Better Interaction with the User)
如果只有一個線程,那一個程式在同一時刻只能做一件事情。以文書處理程式為例,當第一個文檔正在格式化和排隊列印時,可以開啟第二個文檔那該多好啊。在一些早前的文書處理軟體中,當使用者想要列印一個文檔時,如果該文檔正在準備並發送給印表機時,他只好等待。(即,使用者不能在等待列印同時,用該軟體做其他事情)現代文書處理軟體開發利用了多線程可以同時做兩件事情。在單一處理器(one-processor)的系統中,可通過作業系統在兩個任務之間快速的來回切換來類比此情境,以此來實現更好的與使用者互動。
從微處理器的角度來看,即使是最快的打字員在按鍵之間也花費了巨大的時間。在這些大量的時間間隙,處理器可以處理其他任務。當另外一個線程停止了其他工作時,如果一個線程總在等待給使用者的動作一個快速的響應,比如點擊滑鼠或按鍵,使用者將會從系統中感覺到更好的響應。
類比同時進行的活動(Simulation of Simultaneous Activities)
即使只有一個處理器,Java中的線程好像也是並發運行。處理器運行每個線程一小段時間,並線上程之間切換來類比並發執行。這使得每個線程看似都獨立擁有自己的處理器。利用這一特性,你可以使得多個任務好像正在同時發生。實際上是在上下文(Context)切換到另外線程之前,每個線程只運行很短時間。
開發利用多處理器(Exploitation of Multiple Processors)
一些機器中有多個處理器。如果當前的作業系統和JVM的實現開發利用了多處理器,多線程的Java程式能實現真正的線程並發執行。而Java程式不需要修改,因為它已經用了線程。就像這些線程同時運行在不同的處理器上一樣,它只能運行得更快。
當等待慢的I/O操作時,可以做其他事情(Do Other Things While Waiting for Slow IO Operations)
相對於處理器中的代碼執行速度,對磁碟的輸入輸出操作,特別是跨網路的操作,速度相對來說是慢的。結果為了等待完成,讀寫操作可能被阻塞(block)相當長時間。
在java.io包中,類InputStream有一個read()方法會被阻塞,直到從流(stream)中讀一個位元組或者拋出一個IOException異常。當等待流上位元組的到來時,執行該方法的線程不能做其他事情。如果建立了多線程,當某線程被阻塞後,其他線程就可以完成其他活動。
例如,你有一個包含多個TextField組件的Java Applet程式,顯示了兩個線程是如何給使用者提供更好的使用者互動的虛擬碼。第一個線程是GUI事件線程,該線程大部分時間都在waiForNextEvent()方法中被阻塞。第二個線程是背景工作執行緒,它初始化為阻塞狀態,在waitUntilSignalled()方法中等待一個繼續工作的訊號。當所有的TextField組件產生之後,使用者點擊了傳輸資料按鈕。GUI事件線程被解除阻塞,進入deliverEventToListenser()方法,隨又調用actionPerformed()方法,該方法給背景工作執行緒一個訊號,然後立即返回到waitForNextEvent()方法。背景工作執行緒被解除阻塞,離開了waitUntilSignaled()方法,進入gatherDataAndTransmit()方法。背景工作執行緒收集使用者輸入的資料,進行傳輸,當等待伺服器確認資訊時又被阻塞。當讀取確認資訊之後,背景工作執行緒返回waitUntilSignalled()方法。
簡化對象建模(Simplify Object Modeling)
在構建一個系統,需要對系統按照物件導向進行分析,這可能要求某些對象以線程方式運行。這類對象可被認為是主動的(active),與消極(passive)相對應。一個消極對象只有當該對象的方法被調用是才能修改其內部狀態。一個積極對象可以自動改變內部的狀態。
例如,構建一個數字時鐘的圖形化組件,該組件顯示當前系統小時和分鐘。每隔60秒,組件中的分鐘將會改變。最簡單的設計是在時鐘組件內建立一個線程,該線程專門負責修改數字。如果不這樣的話,就需要一個外部線程,它除了完成其它任務外,還需要不斷地監測是否需要更新數字。如果這個外部線程需要從InputStream讀取資料,此時就會被阻塞,如果它等待一個位元組的時間超過一分鐘,那又該怎如何呢?所以在這裡,我們可以利用多線程編程的優勢來簡化問題的解決。