CAS你知道嗎?

CAS 是什麼???

1、比較並交換(CompareAndSet)通過簡單的Demo來看

package com.brian.interview.study.thread;

/**
 * Copyright (c) 2020 ZJU All Rights Reserved
 * <p>
 * Project: JavaSomeDemo
 * Package: com.brian.interview.study.thread
 * Version: 1.0
 * <p>
 * Created by Brian on 2020/2/11 0:47
 */

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 1、  CAS是什麼? ===> CompareAndSet
 *         比較並交換
 */
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);

        // main do thing ......

        System.out.println(atomicInteger.compareAndSet(5, 2020)+"\t current data: "+atomicInteger.get());  // atomicInteger 本來就是5,修改成功

        System.out.println(atomicInteger.compareAndSet(5, 1024)+"\t current data: "+atomicInteger.get());  // atomicInteger 現在是2020,不是5,修改失敗
    }
}

2、CAS 底層原理?同時談談 Unsafe 的理解

atomicInteger.getAndIncrement() 方法的源代碼:

在這裏插入圖片描述
引出來一個問題:Unsafe 類什麼?

Unsafe 類:

在這裏插入圖片描述
1、Unsafe
是CAS的核心類,由於Java方法無法直接訪問底層系統,需要通過本地(native)方法來訪問,Unsafe相當於一個後門,基於該類可以直接操作特定內存的數據。Unsafe類存在於sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,因爲Java中CAS操作的執行依賴於Unsafe類的方法。
注意Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務

2、變量 valueOffset,表示該變量值在內存中的偏移地址,因爲Unsafe就是根據內存偏移地址獲取數據的。
在這裏插入圖片描述
3、變量 value 用 volatile 修飾,保證了多線程之間的內存可見性。

CAS 是什麼:

unsafe.getAndAddInt

var1 AtomicInteger 對象本身
var2 該對象值得引用地址
var4 需要變動的數量
var5 是用過 var1 var2 找出的主內存中真實的值
用該對象當前的值與 var5 比較:
如果相同,更新 var5+var4 並且返回 true,
如果不同,繼續取值然後再比較,直到更新完成。
在這裏插入圖片描述
假設線程A和線程B兩個線程同時執行 getAndAddInt 操作(分別跑在不同CPU上):
1、AtomicInteger 裏面的 value 原始值爲 3,即主內存中 AtomicInteger 的 value 爲 3,根據 JMM 模型,線程A和線程B各自持有一份值爲3的value的副本分別到各自的工作內存。

2、線程A通過 getIntVolatile(var1, var2) 拿到 value 值 3,這時線程A被掛起。

3、線程B也通過 getIntVolatile(var1, var2) 方法獲取到 value 值3,此時剛好線程B沒有被掛起並執行 compareAndSwapInt 方法比較內存值也爲3,成功修改內存值爲4,線程B打完收工,一切OK。

4、這時線程A恢復,執行 compareAndSwapInt 方法比較,發現自己手裏的值數字3和主內存的值數字4不一致,說明該值已經被其它線程搶先一步修改過了,那A線程本次修改失敗,只能重新讀取重新來一遍了

5、線程A重新獲取 value 值,因爲變量 value 被 volatile 修飾,所以其它線程對它的修改,線程A總是能夠看到,線程A繼續執行 compareAndSwapInt 進行比較替換,直到成功。

底層彙編

Unsafe類中的 compareAndSwapInt,是一個本地方法,該方法的實現位於 unsafe.cpp 中

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
    UnsafeWrapper("Unsafe_CompareAndSwapInt");
    cop p = JNIHandles::resolve(obj);
    jint* addr = (jint*)index_oop_from_field_offset_long(p, offset);
    return (jint)(Atomic::cmpxchg(x, addr, e))==e;
UNSAFE_END
// 先想辦法拿到變量 value 在內存中的地址。
// 通過 Atomic::cmpxchg 實現比較替換,其中參數x是即將更新的值,參數e是原內存的值。
簡單版小總結

CAS (CompareAndSwap)
比較當前工作內存中的值和主內存中的值,如果相同則執行規定操作,
否則繼續比較直到主內存和工作內存中的值一致爲止。

CAS應用
CAS有3個操作數,內存值V,舊的預期值A,要修改的更新值B。
當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做。

3、CAS 缺點

循環時間長開銷很大

我們可以看到 getAndAddInt 方法執行時,有個 do while
在這裏插入圖片描述
如果CAS失敗,會一直進行嘗試。如果CAS長時間一直不成功,可能會給CPU帶來很大的開銷。

只能保證一個共享變量的原子操作

當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,

但是

對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖來保證原子性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章