最近私下做一專案,一bug幾日未解決,總惶恐。一日頓悟,bug不可怕,怕的是專案不存在bug,與其懼怕,何不與其剛正面。
系列文章傳送門:
Java多執行緒學習(二)synchronized關鍵字(1)
Java多執行緒學習(二)synchronized關鍵字(2)
Java多執行緒學習(四)等待/通知(wait/notify)機制
本節思維導圖:
關注微信公眾號:“Java面試通關手冊” 回覆“Java多執行緒”獲取思維導圖原始檔和思維導圖軟體。多執行緒就一定好嗎?快嗎??
併發程式設計的目的就是為了能提高程式的執行效率提高程式執行速度,但是併發程式設計並不總是能提高程式執行速度的,而且併發程式設計可能會遇到很多問題,比如:記憶體洩漏、上下文切換、死鎖還有受限於硬體和軟體的資源閒置問題。
多執行緒就是幾乎同時執行多個執行緒(一個處理器在某一個時間點上永遠都只能是一個執行緒!即使這個處理器是多核的,除非有多個處理器才能實現多個執行緒同時執行)。CPU通過給每個執行緒分配CPU時間片來實現偽同時執行,因為CPU時間片一般很短很短,所以給人一種同時執行的感覺。
上下文切換
當前任務在執行完CPU時間片切換到另一個任務之前會先儲存自己的狀態,以便下次再切換會這個任務時,可以再載入這個任務的狀態。任務從儲存到再載入的過程就是一次上下文切換。
上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統來說意味著消耗大量的 CPU 時間,事實上,可能是作業系統中時間消耗最大的操作。 Linux相比與其他作業系統(包括其他類 Unix 系統)有很多的優點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。
那麼我們現在可能會考慮 :如何減少上下文切換的次數呢???
減少上下文切換
這是《Java併發程式設計的藝術》的作者方騰飛大佬嗎????上下文切換又分為2種:讓步式上下文切換和搶佔式上下文切換。前者是指執行執行緒主動釋放CPU,與鎖競爭嚴重程度成正比,可通過減少鎖競爭和使用CAS演算法來避免;後者是指執行緒因分配的時間片用盡而被迫放棄CPU或者被其他優先順序更高的執行緒所搶佔,一般由於執行緒數大於CPU可用核心數引起,可通過適當減少執行緒數和使用協程來避免。
總結一下:
- 減少鎖的使用。因為多執行緒競爭鎖時會引起上下文切換。
- 使用CAS演算法。這種演算法也是為了減少鎖的使用。CAS演算法是一種無鎖演算法。
- 減少執行緒的使用。人物很少的時候建立大量執行緒會導致大量執行緒都處於等待狀態。
- 使用協程。
我們上面提到了兩個名詞:“CAS演算法” 和 “協程”。可能有些人不是很瞭解這倆東西,所以這裡簡單說一下。。。
CAS演算法
CAS(比較與交換,Compare and swap) 是一種有名的無鎖演算法。無鎖程式設計,即不使用鎖的情況下實現多執行緒之間的變數同步,也就是在沒有執行緒被阻塞的情況下實現變數的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。實現非阻塞同步的方案稱為“無鎖程式設計演算法”( Non-blocking algorithm)。 相對應的,獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的情況,並且只有在確保其它執行緒不會造成干擾的情況下執行,會導致其它所有需要鎖的執行緒掛起,等待持有鎖的執行緒釋放鎖。
協程
協程也可以說是微執行緒或者說是輕量級的執行緒,它佔用的記憶體更少並且更靈活。很多程式語言中都有協程。Lua, Ruby 等等都有自己的協程實現。Go完全就是因為協程而發展壯大的。維基百科上面並沒有Java實現協程的方式,但是不代表Java不能實現協程。比如可以使用Java實現的開源協程庫:Quasar。Quasar官網:www.paralleluniverse.co/quasar/,。這個庫實現了一種可以和Go語言中的Goroutine相對標的程式設計概念:Fiber。Fiber是一種真正的協程。
最後Mark兩篇關於協程的文章:
協程,高併發IO終極殺器(3):zhuanlan.zhihu.com/p/27590299
次時代Java程式設計(一):Java裡的協程:geek.csdn.net/news/detail…
避免死鎖
在作業系統中,死鎖是指兩個或兩個以上的程式在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程式稱為死鎖程式。
線上程中,如果兩個執行緒同時等待對方釋放鎖也會產生死鎖。
鎖是一個好東西,但是使用不當就會造成死鎖。一旦死鎖產生程式就無法繼續執行下去。所以如何避免死鎖的產生,在我們使用併發程式設計時至關重要。
根據《Java併發程式設計的藝術》有下面四種避免死鎖的常見方法:
- 避免一個執行緒同時獲得多個鎖
- 避免一個執行緒在鎖內同時佔用多個資源,儘量保證每個鎖只佔用一個資源
- 嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制
- 對於資料庫鎖,加鎖和解鎖必須在一個資料庫連線裡,否則會出現解鎖失敗的情況
解決資源限制
這裡我覺得《Java併發程式設計的藝術》講的還是挺好的。
什麼是資源限制???
所謂資源限制就是我們在進行併發程式設計時,程式的執行速度受限於計算機硬體資源比如CPU,記憶體等等或軟體資源比如軟體的質量、效能等等。舉個例子:如果說伺服器的頻寬只有2MB/s,某個資源的下載速度是1MB/s,系統啟動10個執行緒下載該資源並不會導致下載速度程式設計10MB/s,所以在併發程式設計時,需要考慮這些資源的限制。硬體資源限制有:頻寬的上傳和下載速度、硬碟讀寫速度和CPU處理速度;軟體資源限制有資料庫的連線數、socket連線數、軟體質量和效能等等。
資源限制引發的問題
在併發程式設計中,程式執行加快的原因是執行方式從序列執行變為併發執行,但是如果如果某段程式的併發執行由於資源限制仍然在序列執行的話,這時候程式的執行不僅不會加快,反而會更慢,因為可能增加了上下文切換和資源排程的時間。
如何解決資源限制的問題
對於硬體資源限制,可以考慮使用叢集並行執行程式。既然單機的資源有限制,那麼就讓程式在多機上執行。比如使用Hadoop或者自己搭建伺服器叢集。
對於軟體資源的限制,可以考慮使用資源池將資源複用。比如使用連線池將資料庫和Socket複用,或者在呼叫對方webservice介面獲取資料時,只建立一個連線。另外還可以考慮使用良好的開源軟體。
在資源限制的情況下如何進行併發程式設計
根據不同的資源限制調整程式的併發度,比如下載檔案程式依賴於兩個資源-頻寬和硬碟讀寫速度。有資料庫操作時,設計資料庫練連線數,如果SQL語句執行非常快,而執行緒的數量比資料庫連線數大很多,則某些執行緒會被阻塞,等待資料庫連線。
參考:
維基百科,百度百科
《Java併發程式設計的藝術》
上下文切換的詳解:http://ifeve.com/context-switch-definition/