資源利用率:在某些狀況下,程序必須等待某個外部操做執行完成,例如輸入操做或輸出操做等,而在等待時程序沒法執行其餘任何工做。所以,若是在等待的同時能夠運行另外一個程序,那麼無疑將提升資源的利用率。
公平性:不一樣的用戶和程序對於計算機上的資源有着同等的使用權。一種高效的運行方式是經過粗顆粒度的時間分片(Time Slicing)使這些用戶的程序能共享計算機資源,而不是由一個程序從頭運行到尾,而後再啓用下一個程序。java
便利性:一般來講,在計算多個任務時,應該編寫多個程序,每一個程序執行一個任務並在必要時互相通訊,這比只編寫一個程序來計算全部任務更容易實現。編程
若是使用得當,線程能夠有效地下降程序的開發和維護成本,同時提高複雜應用程序的性能。線程可以將大部分的異步工做流轉換成串行工做流,所以能更好地模擬人類的工做方式和交互方式。此外,線程還能夠下降代碼的複雜度,使代碼更容易編寫、閱讀和維護。緩存
在GUI(Graphic User Interface,用戶圖形界面)應用程序中,線程能夠提升用戶界面的響應靈敏度,而在服務器應用程序中,能夠提高資源利用率以及系統吞吐率。線程還能夠簡化JVM的實現,垃圾收集器一般在一個或多個專門的線程中運行。在許多重要的Java應用程序中,都在必定程度上用到了線程。安全
過去,多處理器系統的很是昂貴和稀少的。但如今,多處理器系統日益普及,而且價格也不斷地下降,即便在低端服務器和中端桌面系統,一般也會採用多個處理器。服務器
因爲基本的調度單位是線程,所以若是在系統中只有一個線程,那麼最多同時只能在一個處理器上運行。在雙處理器系統上,單線程的程序只能使用一半的CPU次元,而在擁有100個處理器的系統上,將有99%的資源沒法使用。另外一方面,多線程程序能夠同時在多個處理器上執行。若是設計正確,多線程程序能夠經過提升處理器資源的利用率來提高系統的吞吐率。多線程
使用多個線程還有助於在單處理器系統上得到更高的吞吐率。若是程序是單線程的,那麼當程序等待某個同步I/O操做完成時,處理器將處於空閒狀態。而在多線程程序中,若是一個線程在等待I/O操做完成,另外一個線程能夠繼續運行,使程序可以在I/O阻塞期間繼續運行。併發
一般,當只須要執行一種類型的任務時,在時間管理方面比執行多種類型的任務要簡單。當只有一種類型的任務須要完成時,只須要埋頭工做,知道完成全部任務,你不須要花任何經理來琢磨下一步該作什麼。框架
例如Servlet和RMI(Remote Method Invocation,遠程方法調用)。框架負責解決一些細節問題,例如請求管理、線程建立、負載平衡,並在正確的時刻將請求分發給正確的應用程序組件。編寫Servlet的開發人員不須要了解有多少請求在同一時刻被處理,也不須要了解套接字的輸入流或者輸出流是否被阻塞。當調用Servlet的servlet方法來響應Web請求時,能夠以同步方式來處理這個請求,就好像它是一個單線程程序。這種方式能夠簡化組件的開發,並縮短掌握這種框架的學習時間。異步
服務器應用程序接受來自多個遠程客戶端的套接字鏈接請求時,若是爲每一個連接都分配其各自的線程並使用同步I/O,那麼就會下降這類程序的開發難度。工具
若是某個應用程序對套接字執行讀取操做而此時尚未數據到來,那麼這個讀取將一直阻塞,直到有數據到達。在單線程程序中,這不只意味着在處理請求的過程當中將停頓,並且還意味着在這個線程被阻塞期間,對全部請求的處理都將停頓。爲了不這個問題,單線程服務器應用程序必須使用非阻塞I/O,這種I/O的複雜性要遠遠高於同步I/O,而且很容易出錯。然而,若是每一個請求都擁有本身的處理線程,那麼在處理某個請求時發生的阻塞將不會影響其餘請求的處理。
在現代GUI應用程序中,例如AWT和Swing等工具,都採用一個事件分發線程(Event Dispatch Thread, EDT)來代替主事件循環。當某個用戶界面事件發生時,在事件線程中將調用應用程序的事件處理器。因爲大多數GUI框架都是單線程子系統,所以到目前爲止仍然在主事件循環,但它如今處理GUI工具的控制下並在其本身的線程中運行,而不是在應用程序的控制下。
Java對線程的支持實際上是一把雙刃劍。雖然Java提供了相應的語言和庫,以及一種明確的跨平臺內存模型,這些工具簡化了併發應用程序的開發,但同時也提升了對開發人員的技術要求,由於在更多的程序中會使用線程。
線程安全性多是很是複雜的,在沒有充足同步的狀況下,多個線程中操做執行順序是不可預測的,甚至會產生奇怪的結果。
// 線程不安全 public class UnsafeSequence { private int value; // 返回一個惟一的數值 public int getNext() { return value++; } }
在上面的代碼中,UnsafeSequence類中將產生一個整數值序列,該序列中的每一個值都是惟一的。在這個類中簡要地說明了多個線程之間的交替操做將如何致使不可預料的結果。在單線程環境中,這個類能正確的工做,但在多線程環境中則不能。
UnsafeSequence的問題在於,若是執行的時機不對,那麼兩個線程在調用getNext時會獲得相同的值。雖然遞增運算看上去是單個操做,但事實上它包含三個獨立的操做:讀取value,將value加1,並將計算結果寫入value。因爲運行時可能將多個線程之間的操做交替執行,所以這兩個線程可能同時執行讀取操做,從而使他們獲得相同的值,並都將這個值加1。結果就是,在不一樣線程的調用中返回了相同的數值。
UnsafeSequence類中說明的是一種常見的併發安全問題,成爲競態條件(Race Condition)。在多線程環境下,getValue是否會返回惟一的值,要取決於運行時對線程中操做的交替執行方式,這並非咱們但願看到的。
因爲多個線程要共享相同的內存地址空間,而且是併發運行,所以它們可能會訪問或修改其它線程正在使用的變量。固然,這是一種極大的便利,由於這種方式比較其餘線程間的通訊機制更容易實現數據共享。但它一樣也帶來了巨大的風險:線程會因爲沒法預料的數據變化而發生錯誤。當多個線程同時訪問和修改相同的變量時,將會在串行編程模型中引入非串行因素,而這種非串行性是很難分析的。要使多線程程序的行爲可預測,必須對共享變量的訪問操做進行協同,這樣纔不會在線程之間發生彼此干擾。
經過將getNext修改成一個同步方法,能夠修改UnsafeSequence中的錯誤:
// 線程安全 public class UnsafeSequence { private int value; // 返回一個惟一的數值 public synchronized int getNext() { return value++; } }
若是沒有同步,那麼不管是編譯器、硬件仍是運行時,均可以隨意安排操做的執行時間和順序。雖然這些技術有助於實現更優秀的性能,而且一般也是值得采用的方法,但它們也爲開發人員帶來了負擔,由於開發人員必須找出這些數據在哪些位置被多個線程共享,只有這樣才能使這些優化措施不破壞線程安全性。
在開發代碼時,必定要注意線程安全性是不可破壞的。安全性不只對於多線程程序很重要,對於單線程程序一樣重要。此外,線程還會致使一些在單線程程序中不會出現的問題,例如活躍性問題。
安全性的含義是「永遠不發生糟糕的事情」,而活躍性則關注與另外一個目標,即「某件正確的事情最終會發生」。當某個操做沒法繼續執行下去時,就會發生活躍性問題。在串行程序中,活躍性問題的形式之一就是無心中形成無限循環,從而使循環以後的代碼沒法獲得執行。線程將帶來一些其餘的活躍性問題。包括死鎖、飢餓、以及活鎖。與大多數併發性錯誤同樣,致使活躍性問題的錯誤一樣是難以分析的,由於它們依賴於不一樣 線程的事件發生時序,所以在開發或者測試中並不老是可以重現。
與活躍性問題密切相關的是性能問題。活躍性意味着某件正確的事情最終會發生,但卻不夠好,由於咱們一般但願正確的事情儘快發生。性能問題包括多個方面,例如服務時間過長,響應不靈敏,吞吐率太低,資源消耗太高,或者可伸縮性較低等。與安全性和活躍性同樣,在多線程程序中不只存在於單線程程序相同的性能問題,並且還存在因爲使用線程而引入的其餘性能問題。
在設計良好的併發應用程序中,線程能提高程序的性能,但不管如何,線程總會帶來某種程度的運行時開銷。在多線程程序中,當線程調度器掛起活躍線程並轉而運行另一個線程時,就會頻繁的出現上下文切換操做(Context Switch),這種操做將帶來極大的開銷:保存和恢復執行上下文,丟失局部性,而且CPU時間將更多地花在線程調度而不是運行上。當線程共享數據時,必須使用同步機制,而這些機制每每會抑制某些編譯器優化,使內存緩存區中的數據無效,以及增長共享內存的總線的同步流量。全部這些因素都將帶來額外的性能開銷。
每一個Java應用程序都會使用線程。當JVM啓動時,它將爲JVM的內部任務建立後臺線程,並建立一個主線程來運行main方法。AWT和Swing的用戶界面框架將建立線程來管理用戶界面事件。Timer將建立線程來執行延遲任務。一些組件框架,例如Servlet和RMI,都會建立線程池並調用這些線程中的方法。
若是要使用這些功能。那麼就必須熟悉併發性和線程安全性,由於這些框架將建立線程而且在這些線程中調用程序中的代碼。雖然將併發性認爲是一種「可選的」或者「高級的」語言功能當然理想,但現實的狀況是,幾乎全部的Java應用程序都是多線程的,所以在使用這些框架時仍然須要對應用程序狀態的訪問進行協同。
當某個框架在應用程序中引入併發性時,一般不可能將併發性僅侷限於框架代碼,由於框架自己會回調(Callback)應用程序的代碼,而這些代碼將訪問應用程序的狀態。一樣,對線程安全性的需求也不能侷限於被調用的代碼,而是要延伸到須要訪問這些代碼所訪問的程序狀態的全部代碼路徑。所以,對線程安全性的需求將在程序中蔓延開來。
框架經過在框架線程中調用應用程序代碼將併發性引入到程序中。在代碼中將不可避免地訪問應用程序狀態,所以全部訪問這些狀態的代碼路徑都必須是線程安全的。