什麼是CAS?
CAS的全稱是 Compare And Swap(Compare And Exchange) 比較並交換,
cas(v,a,b) 變量v,期待值a, 修改值b
CAS底層通過Lock指令實現。
以 java.util.concurrent.atomic包下的 AtomicInteger 爲例
調用的unsafe.getAndAddInt
getAndAddInt 是一個do..while死循環調用compareAndSwapInt方法
compareAndSwapInt 是一個native方法
jdk8u: 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
jdk8u: atomic_linux_x86.inline.hpp
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;
}
最終是通過LOCK_IF_MP 實現。
CAS的ABA問題
什麼是aba問題,打個比方, 要修改的變量a的值原本是1,被另一個線程改成了2之後又改成了1,這個時候1還是1,但它已經被修改過了。
舉個例子:你的女朋友在和你分開後經歷了別的男人又回到你身邊怎麼辦?當然是選擇原諒她
解決方法,加版本號,AtomicStampedReference類
CAS是 樂觀鎖 / 自旋鎖 / 輕量級鎖 ,之前的synchronized是重量級鎖,現在有鎖升級過程。
Synchronized
synchronized鎖的是對象而不是代碼,通過對象頭上的兩位控制是不是加了鎖,加了什麼類型的鎖。
64位hotspot的實現爲
001 表示 無鎖態
101 表示偏向鎖
00 表示輕量級鎖
10 表示重量級鎖
11 GC回收標誌
鎖升級過程
1、對象剛創建時是 無鎖 或者 匿名偏向鎖,
匿名偏向:如果偏向鎖打開了就是匿名偏向鎖,沒打開就是無鎖,匿名偏向鎖在JVM啓動4秒後打開,延遲4秒是因爲JVM啓動的時候就已經知道哪些對象有競爭,就直接設置爲輕量級鎖,如果設置成匿名偏向鎖還要撤銷鎖升級成輕量級鎖,消耗資源。
2、如果有線程上鎖,上偏向鎖,偏向鎖就是把markword的線程ID改爲自己線程ID,下次同一個線程加鎖的時候,不需要爭用,只需要判斷下線程指針是否是同一個,所以,偏向鎖,偏向加鎖的第一個線程。hashcode備份在線程棧上,線程銷燬,鎖降級成無鎖
3、如果有線程競爭撤銷偏向鎖升級爲輕量級鎖(自旋鎖),每個線程都有自己的LockRecord在自己的線程棧上,用CAS去爭用加鎖對象markword的LR的指針,指針指向哪個線程的LR,哪個線程就擁有鎖。
4、如果競爭加劇,升級重量級鎖,向操作系統申請資源,線程掛起,進入等待隊列,等待操作系統的調度。
競爭加劇:JDK1.6之前自旋10次或者自旋線程數超過CPU核心數一般升級重量級鎖,JDK1.6加入自適應自旋,由JVM自己控制。
CAS和Synchronized的對比
CAS底層通過LOCK_IF_MP指令實現,在用戶態執行,不用經過操作系統老大,效率高。但死循環消耗CPU,所以CAS適合線程低競爭,低耗時的場景。
synchronized升級爲重量級鎖後線程交給操作系統老大,效率低,但是加入等待隊列後不消耗CPU,所以適合高競爭,高耗時的場景。