天天看點

深入了解CAS什麼是CAS

什麼是CAS

CompareAndSet  比較交換 :比較目前記憶體和主記憶體中的值,如果達到預期,就進行交換,如果沒有達到,就一直循環。

缺點:1.由于底層是自旋鎖,循環較為耗時 2.一次隻能保證一個共享變量的原子性 3.存在ABA問題

在Java的原子類中存在CAS方法,其方法底層則是調用Unsafe類中的CAS方法。

深入了解CAS什麼是CAS

什麼是Unsafe類:它是Java用來修改記憶體中變量的一個後門類,其底層都是C++對于記憶體變量的直接修改方法。

深入了解CAS什麼是CAS

以AtomicInteger的加法為例,可以看到它的底層就是使用的自旋鎖的CAS方法,我們來解釋一下這個CAS方法的原理

var1是我們傳入進來對象的位址,var2則是指向目前對象位址的值,當我們的var2的值達到var5這個期望值後,我們則會對于var2進行加var4計算 也就是var2的值加1

如果var2沒有達到var5期望值時,則會在自旋鎖中不斷循環。這裡就時直接對于記憶體變量的計算,效率很高。

深入了解CAS什麼是CAS

CAS的ABA問題 ABA(狸貓換太子)

當我們兩個不同線程a,b來對于一個共享變量A進行CAS操作時,可能存在這樣的情況

我們的a線程在擷取A準備計算時,線程b要快一步,對A進行了1置換為3,然後又3置換為1 再傳回給了主記憶體

那這個時候,我們a線程拿到的A變量已經不是原有的變量A了,這就是ABA問題。

深入了解CAS什麼是CAS
package com.gupao.juc.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    //CAS compareAndSet:比較并交換
    public static void main(String args[]){
        AtomicInteger atomicInteger = new AtomicInteger(200);

        //對于我們平時寫的SQL:樂觀鎖
        //期望,更新
        //public final boolean compareAndSet(int expect,int update)
        //如果我期望的值達到了,那麼就更新,否則就不更新 CAS 是CPU的并發原語
        //=============搗亂的線程================//
        System.out.println(atomicInteger.compareAndSet(200, 201));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(201, 200));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(200, 666));
        System.out.println(atomicInteger.get());
    }
}
           

那麼我們要如何來解決ABA問題呢

原子引用

我們需要使用原子引用(相當于樂觀鎖,對于我們的CAS操作加上版本号來進行判斷)

需要用到類:AtomicStampReference

package com.gupao.juc.cas;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo2 {
    public static void main(String[] args) {
        //建構AtomicStampedReference對象,版本号為1
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"版本号===>"+atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName()+"運作結果====>"+atomicStampedReference.compareAndSet(1,
                    2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));//進行一次CAS操作,版本号為2
            //運作完後,列印版本号
            System.out.println(Thread.currentThread().getName()+"版本号====>"+atomicStampedReference.getStamp());

            System.out.println(Thread.currentThread().getName()+"運作結果====>"+atomicStampedReference.compareAndSet(2,
                    1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));//再進行一次CAS操作,版本号為3
            //運作完後,列印版本号
            System.out.println(Thread.currentThread().getName()+"版本号====>"+atomicStampedReference.getStamp());
        },"a").start();
        
        
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"運作結果====>"+atomicStampedReference.compareAndSet(1, 5,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));//另一個線程也做一次CAS操作
            System.out.println(Thread.currentThread().getName()+"版本号===>"+atomicStampedReference.getStamp());
        },"b").start();

    }
}
           
深入了解CAS什麼是CAS

我們此時可以看到,對于每一次的CAS操作,我們都有一次版本号的對應,當我們每成功操作一次CAS後,版本号會+1,否則不會。