並發編程學習(1)----並發編程基礎,----編程基礎
前言:多線程是程式員必須掌握的一項基礎知識,但是由於各種架構封裝使得我們平時很少能接觸到多線程。一旦項目中發生問題,便無從下手。學會了多線程不僅能協助你更好地處理項目中的問題,還能對你理解一些架構有所協助。本系列以《Java並發編程的藝術》為參考,結合網上的一些資料並結合自己的理解整理而成。如有錯誤,歡迎指正。
1、幾個概念
1)進程:是具有一定獨立功能的程式關於某個資料集合上的一次運行活動,進程是系統進行資源分派和調度的獨立單位。一個作業系統中可以同時運行多個任務(程式),每個啟動並執行任務(程式)被稱為一個進程。例如,啟動一個Java程式,作業系統就會建立一個Java進程。
2)線程:它是進程的一個實體,是CPU調度和指派的基本單位,它是比進程更小的能獨立啟動並執行基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程式計數器,一組寄存器和棧),但是它可以與其它同屬一個進程的其他線程共用進程所擁有的全部資源。
3)上線文切換:
即使單核處理器也支援多線程執行代碼,CPU通過給每個線程分配CPU時間片來實現這個機制。時間片是CPU分配給各個線程的時間,因為時間片非常短,所以CPU通過不停地切換線程執行,讓我們感覺多個線程是同時執行的。
CPU通過時間片分配演算法來迴圈執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會儲存上一個任務的狀態,以便下次切換回這個任務時,可以再載入這個任務的狀態。所以任務從儲存到再載入的過程就是一次環境切換。
這就好比我們同時讀兩本書,當在讀一本英文的技術書時,發現某個單詞不認識,於是便開啟字典尋找,但是尋找之前必須先記住這本書已經讀到了多少頁的多少行。等查完單詞以後,能夠繼續讀這本書。這樣的切換是會影響讀書效率的,同樣環境切換也會影響多線程的執行效率。
2.為什麼要使用多線程?
1)充分利用系統資源
由於摩爾定律周期越來越長,處理器效能的提升方式也從更高的主頻向多核發展。線程是大多數作業系統調度的基本單元,而線程是進程的一個實體。一個線程同一時刻只能運行在一個cpu上,這樣就無法充分利用系統資源。如果將計算任務分配給多個處理器核上,就會顯著減少程式的處理時間。
2)更快地回應時間
例如,在一些複雜的業務中,使用者從點擊按鈕開始,服務端需要進行一系列處理過程才能將最終的結果返回給用戶端。使用多線程技術,可以將即時性要求高的資料處理完成以後即將執行結果返回到用戶端。而一些資料一致性不強的操作派發給其它線程處理(如訊息佇列/Worker)。這樣可以縮短回應時間,提升了使用者體驗。
3)更好的編程模型(仿生)
Java為多線程提供了良好的、考究並且一致的編程模型,使得開發人員更加專註於問題的解決。比如,在安卓遊戲開發中,控制一個人物的工作是由多個線程協調完成的,如果使用多線程則很難實現。
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,當使用多線程時,並不會提高下載速度。因為受限於資源,線程仍然是串列執行。程式反而會更慢,由於環境切換和資源調度時間。
對於硬體資源,可以通過增加頻寬或者增加叢集方式解決。對於軟體資源,例如資料庫連接,可以通過控制線程並發數來使得資源使用率最大化。