您現在的位置是:首頁 > 手機遊戲首頁手機遊戲
CAS你以為你真的懂?
- 2022-08-15
xch指令什麼意思
本文轉載自【微信公眾號:小碼逆襲,ID:gh_7c5a039380a0】經微信公眾號授權轉載,如需轉載與原文作者聯絡
01
—
CAS是個啥
CAS(Compare and swap)直譯過來就是比較和替換,也有人叫compare and exchange,是一種透過硬體實現併發安全的常用技術,底層透過利用CPU的CAS指令對快取加鎖或匯流排加鎖的方式來實現多處理器之間的原子操作。仔細觀察J。U。C包中類的實現程式碼,會發現這些類中大量使用到了CAS,所以CAS是Java併發包的實現基礎。它的實現過程是,有3個運算元,記憶體值V,舊的預期值E,要修改的新值N,當且僅當預期值E和記憶體值V相同時,才將記憶體值V修改為N,否則什麼都不做。
02
一圖看懂CAS的操作流程
03
原始碼剖析
從例項中找不同:CAS和重量級鎖
下面看兩個測試程式碼:
public class CasAndUnsafe_01 {
private static int m = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[100];
final CoutDownLatch latch = new CoutDownLatch(threads。length);
Object o = new Object();
for(int i = 0; i < threads。length; i++){
threads[i] = new Thread(()->{
synchronized (o){
for(int j = 0; j <10000; j++){
m++;
}
latch。countDown();
}
});
}
Arrays。stream(threads)。forEach((t)-> t。start());
latch。await();
System。out。println(m);
}
}
程式碼很簡單,每個執行緒都對m做++操作,眾所周知,由於m++不是原子操作,從CPU級別來看,m++經歷了3步,取,加,存3步操作,所以在這之間就有可能出現執行緒併發的問題。加上一個synchronized 重量級鎖就避免了這個問題。
如果不想用synchronized,不想加鎖,可能很多人都用過AtomicInteger。實現如下。
public class AtomicInteger_01 {
private static AtomicInteger m = new AtomicInteger(0);
CountDownLatch latch = new CountDownLatch(threads。length);
for(int j = 0; j < 10000; j++){
//m++
m。incrementAndGet();
latch。countDown();
AtomicInteger 是JUC出來後為大家提供的原子類,這裡面的操作都是原子操作, m。incrementAndGet()增加並獲取,這就是一個CAS操作,我們再也不用加鎖了。
PS:很多人喜歡叫CAS為無鎖,我很不喜歡這個叫法,準確的說CAS是自旋鎖。為什麼說不能叫無鎖呢?
我們從原始碼探究一下CAS底層是怎麼實現的:從程式碼示例2的 m。incrementAndGet()我們跟進去。
/**
* Atomically increments by one the current value。
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe。getAndAddInt(this, valueOffset, 1) + 1;
incrementAndGet你會發現它呼叫了一個類,這個類叫unsafe,呼叫了unsafe類裡面的getAndAddInt。我們跟著進入到getAndAddInt方法中。
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;
這時候你發現getAndAddInt方法中呼叫了compareAndSwapInt方法,從名字不難看出這是一個CAS操作,操作的什麼型別呢?Int型別,那麼具體怎麼操作呢,我們進入到這個方法內。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
當你跟到這裡的時候,你發現這是native 修飾的方法,native什麼意思啊,native說明已經跟到C++的程式碼了。我們就來看一下C++中是怎麼實現的CAS。下面是unsafe。cpp中程式碼實現。
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper(“Unsafe_CompareAndSwapInt”);
oop 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
所以你在java中呼叫了compareAndSwapInt的話,實際上是呼叫了Unsafe_CompareAndSwapInt名字都一樣,由於呼叫鏈很長我就不一一貼程式碼了,感興趣的朋友可以自己找原始碼跟一下,我們直接跳到最根上。
PS:很多人喜歡叫CAS為無鎖,我很不喜歡這個叫法,準確的說CAS是自旋鎖。為什麼說不能叫無鎖呢?
inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) “cmpxchgl %1,(%3)”
: “=a” (exchange_value)
: “r” (exchange_value), “a” (compare_value), “r” (dest), “r” (mp)
: “cc”, “memory”);
return exchange_value;
我們從檔名字 atomic_linux_x86。inline。hpp 中就能獲取到一些資訊,說明到現在位置已經跟具體的平臺和具體的cpu的型號關係了,在x86平臺上linux版本的實現,那麼CAS是怎麼實現的呢?
看這條指令__asm__ volatile (LOCK_IF_MP(%4) “cmpxchgl %1,(%3)” 不知道你明白沒有,
jdk8u:atomic_linux_x86.inline.hpp 的93行
到這一步,你是不是覺得自己已經對於CAS已經理解的很透徹了呢?
jdk8u:atomic_linux_x86.inline.hpp 的93行
這個圖怎麼解釋呢,就是一個執行緒a 取到了0的值,把0改為了1 ,正要把新值寫回記憶體的時候,執行緒2搶先一步修改了記憶體中0的值,會不會有這樣的情況發生呢,答案是肯定的,在併發層級很高的情況下,這種情況是完全可能發生的。問題就在於cmpxchg這條指令是不是原子性的。雖然在彙編層級有一條指令支援CAS,但是很遺憾它不是原子性的,那麼怎麼保證這條指令是原子性的呢?
不知道你們有沒有注意到LOCK_IF_MP(%4),從外觀來看像是加鎖的操作,我們進到LOCK_IF_MP這個方法內。
#define LOCK_IF_MP(mp) “cmp $0, ” #mp “; je 1f; lock; 1: ”
前面的彙編我們忽略,你一定注意到了lock;1這是什麼意思呢,從方法名字看lock if mp ,mp的全稱是Multi Processor,多cpu,意思是什麼呢,就是看你作業系統有多少個處理器,若果只有一個cpu一核的話就不需要原子性了,一定是順序執行的,如果是多核心多cpu前面就要加lock,所以最紅能夠實現CAS的彙編指令就被我們揪出來了。
看到這我們可以下一條結論,原來在CPU層級有一條指令,叫cmpxchg。
。
所以如果你是多核或者多個cpu,CPU在執行cmpxchg指令之前會執行lock鎖定匯流排,實際是鎖定北橋訊號。我不釋放這把鎖誰也過不去,以此來保證cmpxchg的原子性。
04
總結
CAS並不是真的無鎖。
記住這條指令
你有沒有想過cmpxchg這條指令是原子操作嗎?