樂觀鎖,悲觀鎖
阿新 • • 發佈:2017-11-10
包含 並且 調度 更新 原子性 線程沖突 避免 退出 nbsp
悲觀鎖:
總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。傳統的關系型數據庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
Java裏面的同步原語synchronized關鍵字的實現也是悲觀鎖。
樂觀鎖:
顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號,時間戳等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。
Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。
樂觀鎖的一種實現方式-CAS(Compare and Swap 比較並交換):
悲觀鎖:
1,鎖的競爭,加鎖,釋放,引起上下文切換,調度延遲,有性能問題。
2,一個有鎖會導致其他所有需要的線程掛起。
3,優先級高的線程等待優先級低的線程,導致優先級倒置,引起性能風險。
樂觀鎖:
是一種思想,認為數據一般情況下不會產生並發沖突,進行提交更新時,才會對數據是否產生並發沖突進行檢測,出現沖突,失敗了再重來,直到成功為止。
CAS:(compare and swap)
是樂觀鎖技術,多線程使用CAS競爭同一個變量的時候,只有其中一個線程成功,其他都失敗,失敗不會被掛起,會被告知失敗,再次嘗試。
包含三個操作數:需要讀寫的內存位置(V),進行比較的預期原值(A),擬寫入的值(B)
只有V=A,成功,更新為B;
否則不做任何處理;
CAS是非阻塞算法的一種常見實現,性能上有很大提升。
CAS缺點:
1,ABA問題
從Java1.5開始JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置為給定的更新值。
2. 循環時間長開銷大:
自旋CAS(不成功,就一直循環執行,直到成功)如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那麽效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
3. 只能保證一個共享變量的原子操作:
當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合並成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合並一下ij=2a,然後用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行CAS操作。
CAS與Synchronized的使用情景:
1、對於資源競爭較少(線程沖突較輕)的情況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態內核態間的切換操作額外浪費消耗cpu資源;而CAS基於硬件實現,不需要進入內核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。2、對於資源競爭嚴重(線程沖突嚴重)的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低於synchronized。
補充: synchronized在jdk1.6之後,已經改進優化。synchronized的底層實現主要依靠Lock-Free的隊列,基本思路是自旋後阻塞,競爭切換後繼續競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程沖突較少的情況下,可以獲得和CAS類似的性能;而線程沖突嚴重的情況下,性能遠高於CAS。
樂觀鎖,悲觀鎖