Java併發性和多執行緒介紹、優缺點
阿新 • • 發佈:2019-02-09
在過去單核CPU時代,單任務在一個時間點只能執行單一程式。之後發展到多工階段,計算機能在同一時間點並行執行多工或多程序。雖然並不是真正意義上的“同一時間點”,而是多個任務或程序共享一個CPU,並交由作業系統來完成多工間對CPU的執行切換,以使得每個任務都有機會獲得一定的時間片執行。
隨著多工對軟體開發者帶來的新挑戰,程式不在能假設獨佔所有的CPU時間、所有的記憶體和其他計算機資源。一個好的程式應該 在其不再使用資源時對資源進行釋放,以使得其他程式能有機會使用這些資源。
總結:單核CPU上的多執行緒,只是由作業系統來完成多工間對CPU的執行切換。並非真正意義上的併發。
隨著多核CPU的出現,也就 意味著不同的執行緒能被不同的CPU核得到真正意義的並行執行,故而多執行緒技術得到廣泛應用。
併發多執行緒程式設計的弊端:
是:
從磁碟中讀取檔案的時候,大部分的CPU時間用於等待磁碟去讀取資料。在這段時間裡,CPU非常的空閒。它可以做一些別的事情。通過改變操作的順序,就能夠更好的使用CPU資源。看下面的順序:
CPU等待第一個檔案被讀取完。然後開始讀取第二個檔案。當第二檔案在被讀取的時候,CPU會去處理第一個檔案。記住,在等待磁碟讀取檔案的時候,CPU大部分時間是空閒的。
總的說來,CPU能夠在等待IO的時候做一些其他的事情。這個不一定就是磁碟IO。它也可以是網路的IO,或者使用者輸入。通常情況下,網路和磁碟的IO比CPU和記憶體的IO慢的多。
程式設計更簡單
在單執行緒應用程式中,如果你想編寫程式手動處理上面所提到的讀取和處理的順序,你必須記錄每個檔案讀取和處理的狀態。相反,你可以啟動兩個執行緒,每個執行緒處理一個檔案的讀取和操作。執行緒會在等待磁碟讀取檔案的過程中被阻塞。在等待的時候,其他的執行緒能夠使用CPU去處理已經讀取完的檔案。其結果就是,磁碟總是在繁忙地讀取不同的檔案到記憶體中。這會帶來磁碟和CPU利用率的提升。而且每個執行緒只需要記錄一個檔案,因此這種方式也很容易程式設計實現。
程式響應更快
將一個單執行緒應用程式變成多執行緒應用程式的另一個常見的目的是實現一個響應更快的應用程式。設想一個伺服器應用,它在某一個埠監聽進來的請求。當一個請求到來時,它去處理這個請求,然後再返回去監聽。
伺服器的流程如下所述:
while(server is active){
listen for request
process request
}
如果一個請求需要佔用大量的時間來處理,在這段時間內新的客戶端就無法傳送請求給服務端。只有伺服器在監聽的時候,請求才能被接收。另一種設計是,監聽執行緒把請求傳遞給工作者執行緒(worker thread),然後立刻返回去監聽。而工作者執行緒則能夠處理這個請求併發送一個回覆給客戶端。這種設計如下所述:
while(server is active){
listen for request
hand request to worker thread
}
這種方式,服務端執行緒迅速地返回去監聽。因此,更多的客戶端能夠傳送請求給服務端。這個服務也變得響應更快。
桌面應用也是同樣如此。如果你點選一個按鈕開始執行一個耗時的任務,這個執行緒既要執行任務又要更新視窗和按鈕,那麼在任務執行的過程中,這個應用程式看起來好像沒有反應一樣。相反,任務可以傳遞給工作者執行緒(word thread)。當工作者執行緒在繁忙地處理任務的時候,視窗執行緒可以自由地響應其他使用者的請求。當工作者執行緒完成任務的時候,它傳送訊號給視窗執行緒。視窗執行緒便可以更新應用程式視窗,並顯示任務的結果。對使用者而言,這種具有工作者執行緒設計的程式顯得響應速度更快。
多執行緒程式也有一些弊端,如設計複雜上升,資料共享使資料唯一性難以控制,增加資源消耗
設計更復雜:
一般多執行緒程式比單執行緒程式的設計要複雜。在多執行緒訪問共享資料的時候,這部分程式碼需要特別的注意。執行緒之間的互動往往非常複雜。不正確的執行緒同步產生的錯誤非常難以被發現,並且重現以修復。
上下文切換的開銷:
當CPU從執行一個執行緒切換到執行另外一個執行緒的時候,它需要先儲存當前執行緒的本地的資料,程式指標等,然後載入另一個執行緒的本地資料,程式指標等,最後才開始執行。這種切換稱為“上下文切換”(“context switch”)。CPU會在一個上下文中執行一個執行緒,然後切換到另外一個上下文中執行另外一個執行緒。上下文切換並不廉價。如果沒有必要,應該減少上下文切換的發生。
增加資源消耗:執行緒在執行的時候需要從計算機裡面得到一些資源。除了CPU,執行緒還需要一些記憶體來維持它本地的堆疊。它也需要佔用作業系統中一些資源來管理執行緒。我們可以嘗試編寫一個程式,讓它建立100個執行緒,這些執行緒什麼事情都不做,只是在等待,然後看看這個程式在執行的時候佔用了多少記憶體。
- 如果一個執行緒在讀一個記憶體時,另一個執行緒正向該記憶體進行寫操作,那進行讀操作的那個執行緒將獲得什麼結果呢?是寫操作之前舊的值?還是寫操作成功之後的新值?或是一半新一半舊的值?
- 如果是兩個執行緒同時寫同一個記憶體,在操作完成後將會是什麼結果呢?是第一個執行緒寫入的值?還是第二個執行緒寫入的值?還是兩個執行緒寫入的一個混合值?
- 因此如沒有合適的預防措施,任何結果都是可能的。而且這種行為的發生甚至不能預測,所以結果也是不確定性的。
- 資源利用率更好
- 程式設計在某些情況下更簡單
- 程式響應更快