CAS是什麼?ABA問題的產生和解決方法

CAS是什麼?

比較並交換(compare and swap)是一條CPU併發原語java

功能

判斷內存中某個位置的值是否爲預期值,若是是則更改成新的值,這個過程是原子的,中間不予許中斷,解決數據一致性問題。mysql

底層原理

Unsafe類

是CAS的核心類,因爲java沒法直接訪問底層系統,須要經過本地(native)方法訪問,Unsafe至關於一個後門,該類能夠直接操做特定的內存數據。 Unsafe類存在於sun.misc包中,其內部方法操做能夠像C的指針同樣直接操做內存,由於java中的CAS依賴於Unsafe類中的方法 算法

注意 Unsafe中的全部方法都是native修飾的,就是說Unsafe中的方法都是直接操做系統底層資源執行任務sql

底層彙編

底層代碼

 // AtomicInteger類中方法:getAndIncrement,調用Unsafe類中的getAndAddInt
 public final int getAndIncrement(){
     return unsafe.getAndAddInt(this,valueOffset,1);
}
//Unsafe類中
 public final int getAndAddInt(Object var1, long var2, int var4) {
     int var5;
     do {
    var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
     return var5;
}

解釋: 多線程

  • var1是AtomicInteger對象自己併發

  • var2是對象值的引用地址this

  • var4是須要變更的數值atom

  • var5是經過var一、var2找出的內存中的真實的值 方法:用對象的值和var5做比較,若是相同,則更新var5+var4而且返回TRUE,若是不一樣則繼續取值而後再比較,直到更新完成spa

 

 

 

 

缺點

一、 循環時間開銷大操作系統

二、只能保證一個共享變量的原子操做

三、 引出ABA問題

 

ABA問題

CAS算法實現一個重要前提須要提取出內存中某時刻的數據並在當下時刻比較並替換,那麼在這個時間差內會致使數據的變化。

舉例:

一個線程one從內存位置V中取出A,這時候另外一個線程two也從內存中取出A,而且線程two進行了一些操做將A變成了B,而後又將V位置的數據變成了A,而這時候線程one進行 CAS操做的時候發現內存中仍然是A,而後one線程提示操做成功。

儘管one線程的CAS操做成功,可是不表明這個線程是沒問題的

 

代碼還原

package com.dayu.inter; import sun.rmi.runtime.NewThreadAction; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; /** * @Author: dayu * @Date: 2019/9/24 15:13 * @Description */
public class ABADemo { public static void main(String[] args) { System.out.println("=========下面是ABA問題的產生=========="); AtomicReference<Integer> atomicReference = new AtomicReference<>(100); new Thread(() -> { System.out.println(atomicReference.compareAndSet(100, 101)+"\t"+atomicReference.get()); System.out.println(atomicReference.compareAndSet(101, 100)+"\t"+atomicReference.get()); }, "t1").start(); new Thread(() -> { //休息一會,讓線程t1先執行一遍ABA的問題
            try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} System.out.println(atomicReference.compareAndSet(100, 2000)+"\t"+atomicReference.get()); }, "t2").start(); //休息一會,確保上面兩個線程執行完畢
        try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();} System.out.println("=========下面是ABA問題的解決=========="); //有點相似於樂觀鎖 //初始值設定100,時間戳(版本號=1)
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第一次獲取版本號"+stamp); //休息一會,等待t4獲取版本號
            try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第2次獲取版本號"+atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第3次獲取版本號"+atomicStampedReference.getStamp()); }, "t3").start(); //t4和t3最初獲取到的版本號一致,
        new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第一次獲取版本號"+stamp); //休息一會,確保t3完成一次ABA
            try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();} boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"\t 是否修改爲功:"+result+"\t 當前真實的版本號:"+atomicStampedReference.getStamp() +"\t 當前真實的值:"+atomicStampedReference.getReference()); }, "t4").start(); } }

 

爲了解決ABA問題,在原子引用類上加上版本號,這個有點相似於mysql的樂觀鎖同樣,每一個線程更改一次都須要更改版本號,那麼多線程同時獲取到同一個版本號的時候也只有一個線程能夠更改爲功。

相關文章
相關標籤/搜索