悲觀鎖和樂觀鎖以及CAS機制

悲觀鎖:

認爲每次獲取數據的時候數據必定會被人修改,因此它在獲取數據的時候會把操做的數據給鎖住,這樣一來就只有它本身可以操做,其餘人都堵塞在那裏。java

樂觀鎖:

認爲每次獲取數據的時候數據不會被別人修改,因此獲取數據的時候並無鎖住整個數據,可是在更新的時候它會去判斷一下要更新的數據有沒有被別人修改過,例如更新前查詢該數據的版本號,更新的時候看看該版本號有沒有被人修改過,若是被人修改過了,那就不會去更新。算法

應用場景:

悲觀鎖:
由於悲觀鎖會鎖住數據,讓其餘人都等待,因此當一個系統併發量不大,並且能夠接收必定延遲的時候能夠選擇悲觀鎖。
樂觀鎖:
由於樂觀鎖會在更新前去查數據,因此比較適合讀多少寫的場景,由於寫操做多的話會形成大量的查詢操做,給系統帶來壓力。例如SVN、Git等版本控制管理器就是應用的樂觀鎖,當你提交數據的時候對比下版本號,若是遠程倉庫的版本號和本地的不同就表示有人已經提交過代碼了,你須要先更新代碼到本地處理一下版本衝突問題,否則是沒有辦法提交的。數據庫

CAS:

CAS是Compare And Set的縮寫,中文意思就是比較和操做,是一個非阻塞算法。它實際上是一個CPU的指令,它會拿內存值和一個給定的值進行比較,若是相等的話就會把內存值更新爲另外一個給定的值。其實CAS就是使用一個樂觀鎖的機制。安全

Java中CAS機制的應用:

從JDK1.5開始java.util.concurrent.atomic包中新增了一些原子類,AtomicInteger、AtomicLong等等,就是專門解決高併發下的同步問題。由於相似i++、++i的操做不是線程安全的,之前咱們都會使用Synchronized關鍵字,可是如今咱們直接使用這些原子類就能夠解決線程安全的問題。下面用代碼來看看有什麼變化。併發

class Test1 {
    private volatile int count = 0;

    public synchronized void increment() {
    //加鎖才能保證線程安全
        count++; 
    }

    public int getCount() {
        return count;
    }
}

class Test2 {
    private AtomicInteger count = new AtomicInteger();

    //使用AtomicInteger以後,不加鎖,也是線程安全的。
    public void increment() {
        count.incrementAndGet();
    }
   
    public int getCount() {
        return count.get();
    }
}

下面這些是AtomicInteger提供的別的方法。高併發

//獲取當前的值
public final int get() 
//獲取當前的值,並設置新的值
public final int getAndSet(int newValue)
//獲取當前的值,並自增
public final int getAndIncrement()
//獲取當前的值,並自減
public final int getAndDecrement() 
//獲取當前的值,並加上預期的值
public final int getAndAdd(int delta)

咱們從源碼的角度看看AtomicInteger是怎麼實現CAS機制的。unsafe是java提供的用來獲取對象內存地址的類,做用是在更新操做時提供「比較並替換」的做用。valueOffset是記錄value自己在內存的地址,value被聲明爲volatile是保證在更新操做時,當前線程能夠拿到value最新的值。this

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

好比incrementAndGet方法,是獲取當前的值並自增。atom

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

咱們進getAndAddInt方法看看,getIntVolatile和compareAndSwapInt都是本地方法,就是經過本地方法來實現CAS機制。確保不出現線程安全問題。線程

public final int getAndSetInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var4));
        return var5;
    }
    
    
 public native int getIntVolatile(Object var1, long var2);
 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

CAS可能會引發的問題

  1. 由於CAS並不會鎖住數據讓其餘線程阻塞,因此其實是自旋鎖的原理。自旋鎖就是當線程獲取鎖的時候發現這個鎖已經被別的線程搶了,它不是阻塞本身,而是一直循環查看這個鎖有沒有被釋放,這就叫自旋鎖。由於一直循環查看因此能夠能會形成CPU負擔太重,最好設置參數限制查看鎖的次數。
  2. 死鎖問題,有一個線程拿到自旋鎖以後,又去拿鎖,例如遞歸的時候會出現這樣的狀況,本身等待本身釋放縮,卡在那裏不動。
  3. ABA問題,這個問題就是說當線程1讀到內存值爲A,而後線程2進來了把內存值改成B,而後又改成了A,這個時候線程1以爲沒有問題,就更新了。通常在數據庫中使用樂觀鎖都會拿版本號做爲對比值,由於版本號會一直增長,沒有重複的,因此不會出現這個問題。Java中也提供了AtomicStampedReference這個類,大體原理也是提供一個版本號來對比。
相關文章
相關標籤/搜索