1-3 Java並發與多線程基礎
1.並發與多線程簡介
最初計算機是單任務的,後來發展到可以並行運行多任務(進程),由操作系統來調度,每個任務可以獲得一個時間片。多任務下,每個任務在使用系統資源結束後需要釋放資源給其他任務。
後來,同一個任務內部發展出多個線程並發操作,會對相同的內存空間進行並發讀寫操作。更現代的計算機伴隨著多核CPU的出現,也就意味著不同的線程能被不同的CPU核得到真正意義的並行執行。有些在多線程中出現的問題會和多任務以及分布式系統中出現的存在類似,因此該系列會將多任務和分布式系統方面作為參考,所以叫法上稱為“並發性”。
2.多線程的優點
- 資源利用率更好
- 程序設計在某些情況下更簡單
- 程序響應更快
資源利用率更好
想象一下,一個應用程序需要從本地文件系統中讀取和處理文件的情景。比方說,從磁盤讀取一個文件需要5秒,處理一個文件需要2秒。處理兩個文件則需要:
1 |
5 秒讀取文件A |
2 |
2 秒處理文件A |
3 |
5 秒讀取文件B |
4 |
2 秒處理文件B |
5 |
--------------------- |
6 |
總共需要 14 秒 |
從磁盤中讀取文件的時候,大部分的CPU時間用於等待磁盤去讀取數據。在這段時間裏,CPU非常的空閑。它可以做一些別的事情。通過改變操作的順序,就能夠更好的使用CPU資源。看下面的順序:
1 |
5 秒讀取文件A |
2 |
5 秒讀取文件B + 2 秒處理文件A |
3 |
2 秒處理文件B |
4 |
--------------------- |
5 |
總共需要 12 秒 |
CPU等待第一個文件被讀取完。然後開始讀取第二個文件。當第二文件在被讀取的時候,CPU會去處理第一個文件。記住,在等待磁盤讀取文件的時候,CPU大部分時間是空閑的。
總的說來,CPU能夠在等待IO的時候做一些其他的事情。這個不一定就是磁盤IO。它也可以是網絡的IO,或者用戶輸入。通常情況下,網絡和磁盤的IO比CPU和內存的IO慢的多。
程序設計更簡單
在單線程應用程序中,如果你想編寫程序手動處理上面所提到的讀取和處理的順序,你必須記錄每個文件讀取和處理的狀態。相反,你可以啟動兩個線程,每個線程處理一個文件的讀取和操作。線程會在等待磁盤讀取文件的過程中被阻塞。在等待的時候,其他的線程能夠使用CPU去處理已經讀取完的文件。其結果就是,磁盤總是在繁忙地讀取不同的文件到內存中。這會帶來磁盤和CPU利用率的提升。而且每個線程只需要記錄一個文件,因此這種方式也很容易編程實現。
程序響應更快
將一個單線程應用程序變成多線程應用程序的另一個常見的目的是實現一個響應更快的應用程序。設想一個服務器應用,它在某一個端口監聽進來的請求。當一個請求到來時,它去處理這個請求,然後再返回去監聽。
服務器的流程如下所述:
1 |
while (server is active){ |
2 |
listen for request |
3 |
process request |
4 |
} |
如果一個請求需要占用大量的時間來處理,在這段時間內新的客戶端就無法發送請求給服務端。只有服務器在監聽的時候,請求才能被接收。另一種設計是,監聽線程把請求傳遞給工作者線程(worker thread),然後立刻返回去監聽。而工作者線程則能夠處理這個請求並發送一個回復給客戶端。這種設計如下所述:
1 |
while (server is active){ |
2 |
listen for request |
3 |
hand request to worker thread |
4 |
} |
這種方式,服務端線程迅速地返回去監聽。因此,更多的客戶端能夠發送請求給服務端。這個服務也變得響應更快。
桌面應用也是同樣如此。如果你點擊一個按鈕開始運行一個耗時的任務,這個線程既要執行任務又要更新窗口和按鈕,那麽在任務執行的過程中,這個應用程序看起來好像沒有反應一樣。相反,任務可以傳遞給工作者線程(word thread)。當工作者線程在繁忙地處理任務的時候,窗口線程可以自由地響應其他用戶的請求。當工作者線程完成任務的時候,它發送信號給窗口線程。窗口線程便可以更新應用程序窗口,並顯示任務的結果。對用戶而言,這種具有工作者線程設計的程序顯得響應速度更快。
3.多線程的代價
從一個單線程的應用到一個多線程的應用並不僅僅帶來好處,它也會有一些代價。不要僅僅為了使用多線程而使用多線程。而應該明確在使用多線程時能多來的好處比所付出的代價大的時候,才使用多線程。如果存在疑問,應該嘗試測量一下應用程序的性能和響應能力,而不只是猜測。
設計更復雜
雖然有一些多線程應用程序比單線程的應用程序要簡單,但其他的一般都更復雜。在多線程訪問共享數據的時候,這部分代碼需要特別的註意。線程之間的交互往往非常復雜。不正確的線程同步產生的錯誤非常難以被發現,並且重現以修復。
上下文切換的開銷
當CPU從執行一個線程切換到執行另外一個線程的時候,它需要先存儲當前線程的本地的數據,程序指針等,然後載入另一個線程的本地數據,程序指針等,最後才開始執行。這種切換稱為“上下文切換”(“context switch”)。CPU會在一個上下文中執行一個線程,然後切換到另外一個上下文中執行另外一個線程。
上下文切換並不廉價。如果沒有必要,應該減少上下文切換的發生。
增加資源消耗
線程在運行的時候需要從計算機裏面得到一些資源。除了CPU,線程還需要一些內存來維持它本地的堆棧。它也需要占用操作系統中一些資源來管理線程。我們可以嘗試編寫一個程序,讓它創建100個線程,這些線程什麽事情都不做,只是在等待,然後看看這個程序在運行的時候占用了多少內存。
轉載自並發編程網 – ifeve.com本文鏈接地址:多線程的優點
1-3 Java並發與多線程基礎