前言:多線程是程式員必須掌握的一項基礎知識,但是由於各種框架封裝使得我們平時很少能接觸到多線程。一旦項目中發生問題,便無從下手。學會了多線程不僅能幫助你更好地處理項目中的問題,還能對你理解一些框架有所幫助。本系列以《Java併發編程的藝術》為參考,結合網上的一些資料並結合自己的理解整理而成。如有錯誤 ...
前言:多線程是程式員必須掌握的一項基礎知識,但是由於各種框架封裝使得我們平時很少能接觸到多線程。一旦項目中發生問題,便無從下手。學會了多線程不僅能幫助你更好地處理項目中的問題,還能對你理解一些框架有所幫助。本系列以《Java併發編程的藝術》為參考,結合網上的一些資料並結合自己的理解整理而成。如有錯誤,歡迎指正。
1、幾個概念
1)進程:是具有一定獨立功能的程式關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的獨立單位。一個操作系統中可以同時運行多個任務(程式),每個運行的任務(程式)被稱為一個進程。例如,啟動一個Java程式,操作系統就會創建一個Java進程。
2)線程:它是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程式計數器,一組寄存器和棧),但是它可以與其它同屬一個進程的其他線程共用進程所擁有的全部資源。
3)上線文切換:
即使單核處理器也支持多線程執行代碼,CPU通過給每個線程分配CPU時間片來實現這個機制。時間片是CPU分配給各個線程的時間,因為時間片非常短,所以CPU通過不停地切換線程執行,讓我們感覺多個線程是同時執行的。
CPU通過時間片分配演算法來迴圈執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,可以再載入這個任務的狀態。所以任務從保存到再載入的過程就是一次上下文切換。
這就好比我們同時讀兩本書,當在讀一本英文的技術書時,發現某個單詞不認識,於是便打開字典查找,但是查找之前必須先記住這本書已經讀到了多少頁的多少行。等查完單詞以後,能夠繼續讀這本書。這樣的切換是會影響讀書效率的,同樣上下文切換也會影響多線程的執行效率。
2.為什麼要使用多線程?
1)充分利用系統資源
由於摩爾定律周期越來越長,處理器性能的提升方式也從更高的主頻向多核發展。線程是大多數操作系統調度的基本單元,而線程是進程的一個實體。一個線程同一時刻只能運行在一個cpu上,這樣就無法充分利用系統資源。如果將計算任務分配給多個處理器核上,就會顯著減少程式的處理時間。
2)更快地響應時間
例如,在一些複雜的業務中,用戶從點擊按鈕開始,服務端需要進行一系列處理過程才能將最終的結果返回給客戶端。使用多線程技術,可以將實時性要求高的數據處理完成以後即將執行結果返回到客戶端。而一些數據一致性不強的操作派發給其它線程處理(如消息隊列/Worker)。這樣可以縮短響應時間,提升了用戶體驗。
3)更好的編程模型
Java為多線程提供了良好的、考究並且一致的編程模型,使得開發人員更加專註於問題的解決。某些類型的問題,例如模擬,沒有併發是很難解決的。大多數人都看到過至少一種形式的模擬,例如電腦游戲中或電影中電腦生成的動畫。完整的模擬通常涉及許多互動式元素,每一個都有自己的“想法”。從編程角度看,模擬每個模擬元素都是獨立的任務----比如游戲中的門與岩石,或者精靈與巫師。(詳細可參考Tink in Java第四版 21.1.2小節)
3.多線程面臨的問題
在併發編程中,需要解決的兩個問題:線程之間如何通信以及線程之間如何同步。通信是指線程之間以何種機制來交換信息,同步是指程式中用於控制不同線程間相對執行順序的機制。
1)線程間的通信機制有兩種:共用記憶體和消息傳遞。
共用記憶體的併發模型中,線程之間共用程式的公共狀態,通過寫-讀記憶體中的公共狀態進行隱式通信。在消息傳遞的併發模型里,線程之間沒有公共狀態,線程間的通信必須通過發送消息來進行顯示通信(比如Java中Object對象的wait()與notify())。
2)同步機制
在併發編程中,尤其是在處理共用資源時,需要將某些操作(例如寫操作)轉化為同步,以免造成難以想象的後果。常見的同步機制有以下兩種:JVM層面,藉助Object的monitor實現(synchronized);通過匯流排鎖或者鎖定記憶體部分區域實現,例如volatile。
4、併發編程挑戰
1)上下文切換。
當線程較多時,頻繁的上線文切換會導致執行效率降低,合理地調整線程數可以將併發執行效率最大化。
2)死鎖。
鎖是非常有用的工具,運用場景非常多。但是如果使用不當可能會帶來死鎖問題,嚴重時可能使系統癱瘓。死鎖即創建兩個線程A和B,A擁有鎖a,同時需要獲取鎖b,而B擁有鎖b,同時需要獲取鎖a。兩者均持有鎖,無法獲取所需鎖。在實際開發中,要避免死鎖的產生。
避免死鎖的常見方法
a.避免一個線程同時獲取多個鎖
b.便面一個線程在鎖內同時占用多個資源,儘量保證每個鎖只占用一個資源。
c.嘗試使用定時鎖,使用lock.tryLock(timeout)
d.對程式進行異常處理,保證程式發生異常時鎖可以正常釋放。
d.對於資料庫鎖,加鎖和解鎖必須在同一個資料庫連接里,否則會出現解鎖失敗問題。
3)資源限制。
資源限制指在併發編程中,受限於電腦硬體資源或軟體資源。例如,一個伺服器的貸款只有2M/s,當使用多線程時,並不會提高下載速度。因為受限於資源,線程仍然是串列執行。程式反而會更慢,由於上下文切換和資源調度時間。
對於硬體資源,可以通過增加帶寬或者增加集群方式解決。對於軟體資源,例如資料庫連接,可以通過控制線程併發數來使得資源利用率最大化。