JAVA併發編程實踐- 線程的優點

1.2  線程的優點
恰當地使用線程時,可以降低開發和維護的開銷,並且能夠提高複雜應用的性能。線程通過把異步的工作流程轉化爲普遍存在的順序流程,使程序模擬人類工作和交互變得更容易了。另一方面,它們可以把複雜、難以理解的代碼轉化爲直接、簡潔的代碼,這樣更容易讀寫及維護。
線程在GUI應用程序中是非常有用的,可用來改進用戶接口的響應性,並且在服務器應用中,用於提高資源的利用率和吞吐量。它們也可以簡化JVM的實現——垃圾收集器(garbage collector)通常運行於一個或多個持續工作的線程之間。大部分至關重要的Java應用都依賴於線程,某種程度上是因爲它們的組織結構需要這樣。
1.2.1  使用多處理器多處理器系統以往比較昂貴、稀少,只用於大的數據中心和科學計算設備。如今,多處理器系統已經比較便宜,數量也增多了;即使低端服務器和中端的桌面系統也常常採用多處理器。這個趨勢只會逐漸增加;因爲處理器很難再提高它的時鐘頻率,取而代之的是,處理器廠商會在一塊芯片上放置更多的處理器內核。所有主要的芯片製造商都開始了這種轉變,並且我們已經顯著地看到機器中處理器數量的增加。
因爲程序調度的基本單元是線程,一個單線程應用程序一次只能運行在一個處理器上。在雙處理器系統中,一個單線程程序,放棄了其中一半的空閒CPU資源;在擁有100個處理器的系統中,這個單線程程序放棄了99%的資源。從另一方面來看,擁有多個活躍線程的程序可以同時在多處理器上運行。在設計良好的情況下,多線程程序通過更有效的利用空閒處理器資源,來提高吞吐量。
使用多線程也可以幫助我們在單處理器系統中實現更佳的吞吐量。如果一個程序是單線程的,這個處理器在等待一個同步I/O操作完成的時候,仍然是空閒的。在一個多線程程序中,當第一個線程等待I/O結束的同時,另外一個線程也可以運行,這樣就使得應用程序在遇到I/O阻塞的時候仍然有進展。(這就像在等待水燒開的時間裏面讀報紙,優於等待水開之後再去讀報。)
1.2.2  模型的簡化當你需要完成的任務全都是同一類型(修改12個bug)的時候,掌控你的時間通常比完成多種類型的任務要容易(修改bug,面試系統管理員的替代候選人,完成你團隊的效率評估,爲你下週的演講製作幻燈片)。當你只有一種任務的時候,你可以從這堆任務的第一個開始,逐一去做,直到將它們全部完成(或者你自己精疲力竭);你不
需要花精力去思考接下來去執行哪個任務。另一方面,管理多重優先級和截止日期,還要在各任務之間切換,這通常會帶來一些成本開銷。
對於軟件來說也是同樣的道理:相對於需要同時管理多種類型的任務,一個順序處理相同類型任務的程序,寫起來更簡單,更少出錯,也更容易測試。在模擬的情況下,爲每一個類型的任務分配一個線程,或者爲每一個元素分配一個線程,提供理想上的順序,並且這樣做可以把域邏輯(domain logic)與時序調度的細節隔絕開來,進行相互交替的操作,進行異步I/O,以及等待資源。一個複雜、異步的流程可以被分解爲一系列更簡單的同步流程,它們中每一個在相互獨立的線程中運行,只有在特定的同步點才進行彼此間的交互。
這些優點通常被一些框架(framework)所使用,比如 Servlets或者遠程方法調用(RMI,Remote Method Invocation)。這些框架需要處理請求管理,線程創建和負載均衡的細節,與此同時還要處理不同部分轉發到合適的組件的相應流程狀態中去。 Servlet的開發者不需要擔心容器究竟同時正在處理多少個請求,或者Socket的輸入輸出流是否阻塞;當一個Servlet的Service方法作爲Web請求的響應被調用時,它可以同步地處理這些請求,就像它是一個單線程程序一樣。這可以簡化組件開發,並且可以使學習曲線變緩。
1.2.3  對異步事件的簡單處理一個服務器應用程序,接受來自多個遠程客戶端的連接,如果每一個連接服務器都爲其分配一個線程,並允許使用同步I/O,這樣的程序開發起來更容易。
如果程序在讀取Socket時沒有可用數據,那麼read方法會被阻塞直到有數據可用。在一個單線程應用程序中,這不僅意味着處理相應的請求停止了,也意味着在線程阻塞期間對所有請求的處理都停止了。爲了避免這樣的問題,單線程服務器程序被迫使用了非阻塞I/O,這要比同步I/O複雜得多,也更容易出錯。然而,如果每一個請求都擁有自己的線程,那麼阻塞就不會影響到其他請求的處理了。
歷史上,操作系統把一個進程能夠創建的線程限制在相對比較少的數量上,大約有幾百個(甚至更少)。因此,操作系統爲多元化的I/O開發了一些高效的機制,比如Unix的select和poll系統調用,爲了訪問這些機制,Java類庫對非阻塞I/O提供了一組包(java.nio)。然而,今天的操作系統在支持更大數量的線程方面有了巨大的進步,這使得即使是在那些擁有許多客戶的平臺上1,“每線程每客戶(thread-per-client)”模型也是現實的。
1.2.4  用戶界面的更佳響應性GUI 應用程序過去通常爲單線程的,這意味着你要麼通過大量輸入事件頻繁地測試整個代碼(這通常雜亂無章且麻煩),要麼執行所有應用代碼,間接地貫穿整個主事件循環(main event loop)。如果從主事件循環中調用的代碼執行的時間過長,那麼直到代碼執行完畢,用戶界面看上去都是凍結的,因爲在控制權返回到主事件循環之前,程序無法執行用戶界面事件。
AWT 和Swing 工具集這樣的現代GUI框架,用事件派發線程(event dispatch thread,EDT)取代了主事件循環。當一個用戶界面事件發生時,比如按下一個按鈕,事件線程會調用程序定義的事件處理器。大部分GUI框架都是單線程化的子系統,所以主事件循環的有效性仍然可以得到體現,但是它運行於它自身線程GUI toolkit的控制下,而並非受控於應用程序。
在事件發生的線程中如果只有短暫的任務,那麼界面總能夠作出響應,因爲事件線程總能夠及時有效地處理好用戶的活動。然而,在事件線程中處理一個長期、耗時的任務,比如一個大文檔的拼寫檢查,或者從網絡上獲取一個資源,這會削減響應的效率。如果用戶在這個任務運行的時候發生了一個新的動作,事件線程能夠開始處理甚至知曉這個動作都會被延遲很久。雪上加霜的情況是,不僅UI失去了響應,而且用戶很可能不能取消這個不愉快的任務,即使程序提供了cancel按鈕,因爲事件線程正在忙碌工作,直到那個冗長的任務結束,才能夠開始處理cancel按鈕的按下事件!但是,如果讓這個耗時的任務運行在單獨的線程裏,那麼事件線程就能夠自由地處理UI事件,使之具有更好的響應能力。
 
發佈了9 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章