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就無法保證操作的原子性,這個時候就可以用鎖來保證原子性。