悲觀者與樂觀者的作事方式徹底不同,悲觀者的人生觀是一件事情我必需要百分之百徹底控制纔會去作,不然就認爲這件事情必定會出問題;而樂觀者的人生觀則相反,凡事無論最終結果如何,他都會先嚐試去作,大不了最後不成功。這就是悲觀鎖與樂觀鎖的區別,悲觀鎖會把整個對象加鎖佔爲自有後纔去作操做,樂觀鎖不獲取鎖直接作操做,而後經過必定檢測手段決定是否更新數據。這一節將對樂觀鎖進行深刻探討。算法
前面用Synchronized互斥鎖屬於悲觀鎖,它有一個明顯的缺點,它無論數據存不存在競爭都加鎖,隨着併發量增長,且若是鎖的時間比較長,其性能開銷將會變得很大。有沒有辦法解決這個問題?答案是基於衝突檢測的樂觀鎖。這種模式下,已經沒有所謂的鎖概念了,每條線程都直接先去執行操做,計算完成後檢測是否與其餘線程存在共享數據競爭,若是沒有則讓此操做成功,若是存在共享數據競爭則可能不斷地從新執行操做和檢測,直到成功爲止,可叫CAS自旋。bash
樂觀鎖的核心算法是CAS(Compare and Swap),它涉及到三個操做數:內存值、預期值、新值。當且僅當預期值和內存值相等時纔將內存值修改成新值。這樣處理的邏輯是,首先檢查某塊內存的值是否跟以前我讀取時的同樣,如不同則表示期間此內存值已經被別的線程更改過,捨棄本次操做,不然說明期間沒有其餘線程對此內存值操做,能夠把新值設置給此塊內存。如圖,有兩個線程可能會差很少同時對某內存操做,線程二先讀取某內存值做爲預期值,執行到某處時線程二決定將新值設置到內存塊中,若是線程一在此期間修改了內存塊,則經過CAS便可以檢測出來,假如檢測沒問題則線程二將新值賦予內存塊。併發
你可能會發現一個疑問,比較和交換,從字面上就有兩個操做了,更別說實際CAS可能會有更多的執行指令,他們是原子性的嗎?若是非原子性又怎麼保證CAS操做期間出現併發帶來的問題?我是否是須要用上節提到的互斥鎖來保證他的原子性操做?CAS確定是具備原子性的,否則就談不上在併發中使用了,但這個原子性是由CPU硬件指令實現保證的,即便用JNI調用native方法調用由C++編寫的硬件級別指令,jdk中提供了Unsafe類執行這些操做。另外,你可能想着CAS是經過互斥鎖來實現原子性的,這樣確實能實現,但用這種方式來保證原子性顯示毫無心義。下面一個僞代碼加深對CAS的理解:public class AtomicInt {
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) {
Unsafe類提供的硬件級別的compareAndSwapInt方法;
}
}
複製代碼
其中最重要的方法是getAndIncrement方法,它裏面實現了基於CAS的自旋。 如今已經瞭解樂觀鎖及CAS相關機制,樂觀鎖避免了悲觀鎖獨佔對象的現象,同時也提升了併發性能,但它也有缺點:機器學習
樂觀鎖是對悲觀鎖的改進,雖然它也有缺點,但它確實已經成爲提升併發性能的主要手段,並且jdk中的併發包也大量使用基於CAS的樂觀鎖。分佈式
-------------推薦閱讀------------高併發
跟我交流,向我提問:
公衆號的菜單已分爲「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。
歡迎關注: