1. 程式人生 > >人生很短,做自己喜歡的事情罷。

人生很短,做自己喜歡的事情罷。

樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠資料庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫效能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基於資料版本( Version )記錄機制實現。何謂資料版本?即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表增加一個 “version” 欄位來實現。讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交資料的版本資料與資料庫表對應記錄的當前版本資訊進行比對,如果提交的資料版本號大於資料庫表當前版本號,則予以更新,否則認為是過期資料。
悲觀鎖

(Pessimistic Lock),正如其名,具有強烈的獨佔和排他特性。它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。

簡而言之:
悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作。[1]
樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。[1] 樂觀鎖不能解決髒讀的問題。
Java中的樂觀鎖和悲觀鎖:我們都知道,cpu是時分複用的,也就是把cpu的時間片,分配給不同的thread/process輪流執行,時間片與時間片之間,需要進行cpu切換,也就是會發生程序的切換。切換涉及到清空暫存器,快取資料。然後重新載入新的thread所需資料。當一個執行緒被掛起時,加入到阻塞佇列,在一定的時間或條件下,在通過notify(),notifyAll()喚醒回來。在某個資源不可用的時候,就將cpu讓出,把當前等待執行緒切換為阻塞狀態。等到資源(比如一個共享資料)可用了,那麼就將執行緒喚醒,讓他進入runnable狀態等待cpu排程。這就是典型的悲觀鎖的實現。獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的情況,並且只有在確保其它執行緒不會造成干擾的情況下執行,會導致其它所有需要鎖的執行緒掛起,等待持有鎖的執行緒釋放鎖。
但是,由於在程序掛起和恢復執行過程中存在著很大的開銷。當一個執行緒正在等待鎖時,它不能做任何事,所以悲觀鎖有很大的缺點。舉個例子,如果一個執行緒需要某個資源,但是這個資源的佔用時間很短,當執行緒第一次搶佔這個資源時,可能這個資源被佔用,如果此時掛起這個執行緒,可能立刻就發現資源可用,然後又需要花費很長的時間重新搶佔鎖,時間代價就會非常的高。
所以就有了樂觀鎖的概念,他的核心思路就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。在上面的例子中,某個執行緒可以不讓出cpu,而是一直while迴圈,如果失敗就重試,直到成功為止。所以,當資料爭用不嚴重時,樂觀鎖效果更好。比如CAS就是一種樂觀鎖思想的應用。
JDK1.5中引入了底層的支援,在int、long和物件的引用等型別上都公開了CAS的操作,並且JVM把它們編譯為底層硬體提供的最有效的方法,在執行CAS的平臺上,執行時把它們編譯為相應的機器指令。在java.util.concurrent.atomic包下面的所有的原子變數型別中,比如AtomicInteger,都使用了這些底層的JVM支援為數字型別的引用型別提供一種高效的CAS操作。
在CAS操作中,會出現ABA問題。就是如果V的值先由A變成B,再由B變成A,那麼仍然認為是發生了變化,並需要重新執行演算法中的步驟。有簡單的解決方案:不是更新某個引用的值,而是更新兩個值,包括一個引用和一個版本號,即使這個值由A變為B,然後為變為A,版本號也是不同的。AtomicStampedReference和AtomicMarkableReference支援在兩個變數上執行原子的條件更新。AtomicStampedReference更新一個“物件-引用”二元組,通過在引用上加上“版本號”,從而避免ABA問題,AtomicMarkableReference將更新一個“物件引用-布林值”的二元組。