cas


是什么

	CAS的全稱為Compare-And-Swap,它是一條CPU並發原語,中文翻譯成比較並交換,實現並發算法時常用到的一種技術,它包含三個操作數——內存位置、預期原值及更新值。執行CAS操作的時候,將內存位置的值與預期原值比較:如果相匹配,那么處理器會自動將該位置值更新為新值,如果不匹配,處理器不做任何操作,多個線程同時執行CAS操作只有一個會成功,這個過程是原子的。

​ 由於CAS是一種系統原語,原語屬於操作系統用語范疇,是由若干條指令組成的,用於完成某個功能的一個過程,並且原語的執行必須是連續的,在執行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的數據不一致問題。

原理

CAS (CompareAndSwap) CAS有3個操作數,內存位置值V,舊的預期值A,要修改的更新值B。當且僅當舊的預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做或重來 。

demo

public class CASdemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(1);
        System.out.println(atomicInteger.compareAndSet(1, 2)+ "   "+ atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(1, 3)+ "   "+ atomicInteger.get());
    }
}

		compareAndSet 源碼
   /**
     *  this:表示要操作的對象
				valueOffset:表示要操作對象中屬性地址的偏移量
				expect:表示需要修改數據的期望的值
				update:表示需要修改為的新值
     * @param args
     */
public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

unsafe 是什么

Unsafe 是CAS的核心類,由於Java方法無法直接訪問底層系統,需要通過本地(native)方法來訪問,Unsafe相當於一個后門,基於該類可以直接操作特定內存的數據。Unsafe類存在於sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,因為Java中CAS操作的執行依賴於Unsafe類的方法。注意Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務;變量valueOffset,表示該變量值在內存中的偏移地址,因為Unsafe就是根據內存偏移地址獲取數據的。

實現類

AtomicInteger,AtomicBoolean,AtomicReference等

自旋鎖

可以利用cas的原理自定義一個簡版的自旋鎖

public class SpinlockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference();
    public void lock () {
        System.out.println(Thread.currentThread().getName()+"進來了");
        while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
        }
        System.out.println(Thread.currentThread().getName()+"搶到鎖了");
    }
    public void unLock () {
        atomicReference.compareAndSet(Thread.currentThread(), null);
        System.out.println(Thread.currentThread().getName()+"開始釋放鎖");
    }

    public static void main(String[] args) throws InterruptedException {
        SpinlockDemo spinlockDemo = new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.lock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {

            }
            spinlockDemo.unLock();
        },"t1").start();
        TimeUnit.SECONDS.sleep(2);
        new Thread(()->{
            spinlockDemo.lock();
            spinlockDemo.unLock();
        },"t2").start();
    }
}

缺點

  1. 如果CAS長時間一直不成功,可能會給CPU帶來很大的開銷。

  2. 會引發ABA問題

    ABA問題的解決辦法:帶版本號的原子引用 AtomicStampedReference

package com.example.juc;

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

/**
 * @author yeric
 * @description:
 * @date 2021/9/28 23:11
 */
public class ABADemo {
    /**
     * 普通的原子引用包裝類
     */
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    // 傳遞兩個值,一個是初始值,一個是初始版本號
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {

        System.out.println("============以下是ABA問題的產生==========");

        new Thread(() -> {
            // 把100 改成 101 然后在改成100,也就是ABA
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "t1").start();

        new Thread(() -> {
            try {
                // 睡眠一秒,保證t1線程,完成了ABA操作
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 把100 改成 101 然后在改成100,也就是ABA
            System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());

        }, "t2").start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }


        System.out.println("============以下是ABA問題的解決==========");

        new Thread(() -> {

            // 獲取版本號
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t 第一次版本號" + stamp);

            // 暫停t3一秒鍾
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 傳入4個值,期望值,更新值,期望版本號,更新版本號
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);

            System.out.println(Thread.currentThread().getName() + "\t 第二次版本號" + atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);

            System.out.println(Thread.currentThread().getName() + "\t 第三次版本號" + atomicStampedReference.getStamp());

        }, "t3").start();

        new Thread(() -> {

            // 獲取版本號
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t 第一次版本號" + stamp);

            // 暫停t4 3秒鍾,保證t3線程也進行一次ABA問題
            try {
                TimeUnit.SECONDS.sleep(3);
            } 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());

            System.out.println(Thread.currentThread().getName() + "\t 當前實際最新值" + atomicStampedReference.getReference());

        }, "t4").start();

    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM