樂觀鎖或者悲觀鎖兑徘,都是一種思想宴偿,而非是真的鎖
悲觀鎖
悲觀鎖,就是不管是否發(fā)生多線程沖突胎源,只要存在這種可能涕蚤,就每次訪問都加鎖愧捕。
syncrhoized是一種獨占鎖次绘,即:占用該鎖的線程才可以執(zhí)行,申請該鎖的線程就只能掛起等待禾进,直到占用鎖的線程釋放鎖才喚醒艇拍,拿到鎖并執(zhí)行卸夕。由于在進程掛起和恢復(fù)執(zhí)行過程中存在著很大的開銷,并且當(dāng)一個線程正在等待鎖時个初,它不能做任何事。所以syncrhoized是一種悲觀鎖,凡是用syncrhoized加了鎖的多線程之間都會因鎖的爭奪結(jié)果導(dǎo)致掛起瓢省、喚醒等開銷。
樂觀鎖
樂觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會產(chǎn)生并發(fā)沖突馒胆,所以在數(shù)據(jù)進行提交更新的時候,才會正式對數(shù)據(jù)是否產(chǎn)生并發(fā)沖突進行檢測型雳,如果發(fā)現(xiàn)并發(fā)沖突了,則讓返回用戶錯誤的信息冤荆,讓用戶決定如何去做乌妒。
上面提到的樂觀鎖的概念中其實已經(jīng)闡述了它的具體實現(xiàn)細節(jié):主要就是兩個步驟:沖突檢測和數(shù)據(jù)更新芥被。其實現(xiàn)方式有一種比較典型的就是 Compare and Swap ( CAS )。
CAS
CAS是樂觀鎖技術(shù)匹中,當(dāng)多個線程嘗試使用CAS同時更新同一個變量時顶捷,只有其中一個線程能更新變量的值,而其它線程都失敗重虑,失敗的線程并不會被掛起,而是被告知這次競爭中失敗提针,并可以再次嘗試。
CAS: 全稱Compare and swap揖曾,字面意思:”比較并交換“,一個 CAS 涉及到以下操作:
我們假設(shè)內(nèi)存中的原數(shù)據(jù)V奴拦,舊的預(yù)期值A(chǔ)绿鸣,需要修改的新值B。
- 比較 A 與 V 是否相等擎厢。(比較)
- 如果比較相等,將 B 寫入 V厘惦。(交換)
- 返回操作是否成功。
Java對CAS的支持
在JDK1.5 中新增java.util.concurrent(J.U.C)就是建立在CAS之上的羡玛。相對于對于synchronized這種阻塞算法浓若,CAS是非阻塞算法的一種常見實現(xiàn)。所以J.U.C在性能上有了很大的提升碌上。
我們以java.util.concurrent中的AtomicInteger為例盔性,看一下在不使用鎖的情況下是如何保證線程安全的后豫。主要理解getAndIncrement方法挫酿,該方法的作用相當(dāng)于 ++i 操作早龟。
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int get() {
return value;
}
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
這里面有一個知識點如:unsafe,先帶著這個疑問我先說代碼意思
在沒有鎖的機制下需要字段value要借助volatile,保證線程間的數(shù)據(jù)是可見的饵骨。這樣在獲取變量的值的時候才能直接讀取翘悉。然后來看看++i是怎么做到的
getAndIncrement采用了CAS操作,每次從內(nèi)存中讀取數(shù)據(jù)然后將此數(shù)據(jù)和+1后的結(jié)果進行CAS操作居触,如果成功就返回結(jié)果妖混,否則重試直到成功為止。而compareAndSet利用JNI來完成CPU指令的操作轮洋。
AtomicInteger中就利用了volatile可見性和CAS的原子性實現(xiàn)了多線程安全
接著Unsafe是啥呢 他就有CAS的功能
Unsafe
java不能直接訪問操作系統(tǒng)底層弊予,而是通過本地方法來訪問。Unsafe類提供了硬件級別的原子操作
主要提供了以下功能:
1、通過Unsafe類可以分配內(nèi)存讼育,可以釋放內(nèi)存;
類中提供的3個本地方法allocateMemory逆航、reallocateMemory蓉坎、freeMemory分別用于分配內(nèi)存罐监,擴充內(nèi)存和釋放內(nèi)存,與C語言中的3個方法對應(yīng)柏锄。
2评架、可以定位對象某字段的內(nèi)存位置塘砸,也可以修改對象的字段值蛉迹,即使它是私有的;
public native long allocateMemory(long l);
public native long reallocateMemory(long l, long l1);
public native void freeMemory(long l);
3祟蚀、掛起與恢復(fù)
將一個線程進行掛起是通過park方法實現(xiàn)的肺孵,調(diào)用 park后匀借,線程將一直阻塞直到超時或者中斷等條件出現(xiàn)瑰艘。unpark可以終止一個掛起的線程,使其恢復(fù)正常键兜。整個并發(fā)框架中對線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法尺棋,但最終都調(diào)用了Unsafe.park()方法封锉。
public class LockSupport {
public static void unpark(Thread thread) {
if (thread != null)
unsafe.unpark(thread);
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
unsafe.park(false, 0L);
setBlocker(t, null);
}
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
unsafe.park(false, nanos);
setBlocker(t, null);
}
}
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
unsafe.park(true, deadline);
setBlocker(t, null);
}
public static void park() {
unsafe.park(false, 0L);
}
public static void parkNanos(long nanos) {
if (nanos > 0)
unsafe.park(false, nanos);
}
public static void parkUntil(long deadline) {
unsafe.park(true, deadline);
}
}
4、CAS操作
是通過compareAndSwapXXX方法實現(xiàn)的
/**
* 比較obj的offset處內(nèi)存位置中的值和期望的值膘螟,如果相同則更新成福。此更新是不可中斷的。
*
* @param obj 需要更新的對象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect與field的當(dāng)前值相同荆残,設(shè)置filed的值為這個新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
CAS操作有3個操作數(shù)奴艾,內(nèi)存值M,預(yù)期值E内斯,新值U握侧,如果M==E,則將內(nèi)存值修改為B嘿期,否則啥都不做
CAS的隱藏問題ABA
CAS算法實現(xiàn)一個重要前提需要取出內(nèi)存中某時刻的數(shù)據(jù)品擎,而在下時刻比較并替換,那么在這個時間差類會導(dǎo)致數(shù)據(jù)的變化备徐。
比如說一個線程one從內(nèi)存位置V中取出A萄传,這時候另一個線程two也從內(nèi)存中取出A,并且two進行了一些操作變成了B蜜猾,然后two又將V位置的數(shù)據(jù)變成A秀菱,這時候線程one進行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功蹭睡。盡管線程one的CAS操作成功衍菱,但是不代表這個過程就是沒有問題的。
部分樂觀鎖的實現(xiàn)是通過版本號(version)的方式來解決ABA問題肩豁,樂觀鎖每次在執(zhí)行數(shù)據(jù)的修改操作時脊串,都會帶上一個版本號辫呻,一旦版本號和數(shù)據(jù)的版本號一致就可以執(zhí)行修改操作并對版本號執(zhí)行+1操作,否則就執(zhí)行失敗琼锋。因為每次操作的版本號都會隨之增加放闺,所以不會出現(xiàn)ABA問題,因為版本號只會增加不會減少缕坎。
https://www.cnblogs.com/qjjazry/p/6581568.html
https://blog.csdn.net/zhengchao1991/article/details/56013248