悲觀鎖:
悲觀鎖悲觀的認為每一次操作都會造成更新丟失問題,在每次查詢時加上排他鎖
每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
Select * from xxx for update;
缺點:因為只能保證一個連接進行操作,所以效率低
樂觀鎖:
樂觀鎖會樂觀的認為每次查詢都不會造成更新丟失,利用版本字段控制
重入鎖:
重入鎖也叫做遞歸鎖,指的是同一線程 外層函數獲得鎖之后 ,內層遞歸函數仍然有獲取該鎖的代碼,但外層函數不受內層函數影響,例如當內部釋放鎖(unlock)后,外部不會釋放。在JAVA環境下 ReentrantLock 和synchronized 都是可重入鎖。
synchronized :
ReentrantLock :
讀寫鎖:
兩個線程同時讀一個資源沒有任何問題,所以應該允許多個線程能在同時讀取共享資源。但是如果有一個線程想去寫這些共享資源,就不應該再有其它線程對該資源進行讀或寫(讀-讀能共存,讀-寫不能共存,寫-寫不能共存)。這就需要一個讀/寫鎖來解決這個問題。Java5在java.util.concurrent包中已經包含了讀寫鎖。
CAS無鎖機制:
原子類底層實現保證線程安全就是通過CAS實現。
CAS算法的過程是這樣:它包含三個參數CAS(V,E,N): V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時,才會將V的值設為N,如果V值和E值不同,則說明已經有其他線程做了更新,則當前線程什么都不做。最后,CAS返回當前V的真實值。
對應java內存模型,V相當於是主內存,E相當於本地內存,如果(V=E),本地內存與主內存一致,說明變量沒有被修改過那么就將V要更新的變量的值設置成N新值,如果不相等,說明本地內存被修改,需要將主內存的值刷新到本地內存中去,再進行V和E進行比較,然后再設置成新值N。
一個來自碼農翻身的例子:
(1)從內存中讀取value值,假設為10,稱之為A
(2)B=A+1,得到B=11
(3)用A的值和內存的值相比,如果相等(過去的一段時間內,沒人修改過A),就把B寫入內存,如果不相等的,說明A在這段時間內被修改了,就放棄這次修改,返回第一步
CAS存在一個很明顯的問題,即ABA問題。
問題:如果變量V初次讀取的時候是A,並且在准備賦值的時候檢查到它仍然是A,那能說明它的值沒有被其他線程修改過了嗎?
如果在這段期間曾經被改成B,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。針對這種情況,java並發包中提供了一個帶有標記的原子引用類AtomicStampedReference,它可以通過控制變量值的版本來保證CAS的正確性。
自旋鎖:
自旋鎖是采用讓當前線程不停地的在循環體內執行實現的,當循環的條件被其他線程改變時 才能進入臨界區。
當一個線程 調用這個不可重入的自旋鎖去加鎖的時候沒問題,當再次調用lock()的時候,因為自旋鎖的持有引用已經不為空了,該線程對象會誤認為是別人的線程持有了自旋鎖
使用了CAS原子操作,lock函數將owner設置為當前線程,並且預測原來的值為空。unlock函數將owner設置為null,並且預測值為當前線程。
當有第二個線程調用lock操作時由於owner值不為空,導致循環一直被執行,直至第一個線程調用unlock函數將owner設置為null,第二個線程才能進入臨界區。
由於自旋鎖只是將當前線程不停地執行循環體,不進行線程狀態的改變,所以響應速度更快。但當線程數不停增加時,性能下降明顯,因為每個線程都需要執行,占用CPU時間。如果線程競爭不激烈,並且保持鎖的時間段。適合使用自旋鎖。
分布式鎖:
如果想在不同的jvm中保證數據同步,使用分布式鎖技術。
有數據庫實現、緩存實現、Zookeeper分布式鎖