JAVA並發編程藝術 一(並發編程的挑戰)
從今天起開始java並發編程藝術的學習,每一章學習完以後再這裏記錄下內容的重點,做個筆記,加深印象。
並發編程的目的是為了讓程序運行的更快,但是,並不是啟動更多的線程就能讓程序最大限度地並發執行。在進行並發是,如果希望通過多現場執行任務讓程序運行得更快,會面臨非常多的挑戰,比如上下文切換的問題,死鎖的問題,以及受限於硬件和軟件的資源限制問題,本章會介紹幾種並發編程的挑戰以及解決方案
1、上下問切換
即使是單核處理器也支持多線程執行代碼,cpu通過給每個線程分配cpu時間片來實現這個機制。時間片是cpu分配給各個線程的時間,因為時間片非常短,所以cpu通過不停地切換線程執行,讓我們感覺多個線程是同時間執行的,時間片一般是幾十毫秒。
cpu通過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,可以再加載這個任務狀態。所以任務從保存到再加載的過程就是一次上下文切換。
這就像我們同時度兩本書,當我們在讀一本英文的技術書時,發現某個單詞不認識,於是便打開中英文字典,但是中放下英文技術書之前,大腦必需先記住這本書讀到了多少頁的第幾行,等查完字典之後,能夠繼續讀這本書。這樣的切換會影響讀書效率,同樣上下文切換也會影響多線程的執行速度。
如何減少上下文切換?
減少上下文切換的方法有無鎖並發編程、CAS算法、使用最少線程和使用協程。
無鎖並發編程。多線程競爭鎖時,會引起上下文切換,所以多線程處理數據時,可以用一些辦法來避免使用鎖,如將數據的ID按照hash算法取模分段,不同的線程處理不同的數據。
CAS算法。java的Atomic包使用了CAS算法更新數據,而不需要加鎖。
使用最少線程。避免創建不需要的線程,比如任務很少,但是創建了很多線程來處理,這樣會造成大量線程都處於等待狀態。
協程。在單線程裏實現多任務調度,並在單線程裏維持多個任務間的切換
2、死鎖
這裏只介紹避免死鎖的幾個常見的方法。
避免一個線程獲同時取多個鎖。
避免一個線程只鎖內同時占用多個資源,盡量保證每個鎖只占用一個資源。
嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。
對於數據庫鎖,加鎖和解鎖必須在一個數據庫連接裏,否則會出現解鎖失敗的情況。
3、資源限制的挑戰
什麽是資源限制?
資源限制是指在進行並發編程時,程序的執行速度受限制於計算機硬件資源或軟件資源。硬件資源限制有寬帶的上傳/下載速度、硬盤讀寫速度和CPU處理速度。軟件限制有數據庫的連接數和socket等問題。
資源限制引發的問題
並發編程中,將代碼執行速度加快的原則是將代碼中串行執行的部分變成並發執行,但是如果將某段串行的代碼並發執行,因為受限於資源,仍然中串行執行,這時候程序不僅不會加快執行,反而會更慢,因為增加了上下文切換和資源調度的時間。
如何解決資源限制的問題
對於硬件資源限制,可以考慮使用集群並行執行程序。既然單機的資源有限制,那麽久讓程序中多機上運行,不同的機器處理不同的數據。可以通過“數據ID%機器數”,計算得到一個機器編號,然後由對應編號的機器處理這筆數據。
對於軟件限制,可以考慮使用資源池將資源復用。比如使用連接池將數據庫和socket連接復用,或者在調用對方webservice接口獲取數據時,只建立一個連接。
在資源限制情況下進行並發編程
根據不同的資源限制調整並發度,比如下載文件程序依賴於兩個資源 寬帶和硬盤讀寫速度。有數據庫操作時,涉及數據連接數,如果SQL語句執行非常快,而線程的數量比數據庫連接數大的多,則某些線程會被阻塞,等待數據庫連接
JAVA並發編程藝術 一(並發編程的挑戰)