CAS
CAS(CompareAndSet)是保證並發安全性的一條CPU底層原子指令,它的功能是判斷某個值是否為預期值,如果是的話,就改為新值,在CAS過程中不會被中斷。
compareAndSet 在JNI(Java Naive Interface)中實現,位於unsafe.cpp文件,關鍵的語句是 cmpxchg(x, addr, e),其中x指的是舊值,addr是要和oldValue一致的內存位置,而e是要變為的新值。執行該原子語句時,將oldValue和從addr取出的值進行比較,相等的話才設置addr位置的值為新值e。
ABA
但CAS存在一個ABA問題,舉例來說,假設線程1和線程2擁有同一個引用p,p指向對象A。某個時刻,線程1想要利用CAS把p指向的對象換成C,此時被線程2中斷,線程2將p指向的對象換成B后再換成A,然后線程1繼續運行,發現p確實仍然指向對象A,因此執行CAS將A換成C。但線程1並不知道在它中斷的這段時間內,p指向的引用經歷了從A到B在到A的過程,這個bug就稱為ABA問題。對於普通場景來說,ABA問題似乎不會造成什么危害,但我們來考慮下面這種場景。
ABA的危害
下面是一段偽代碼,將就着看一下。場景是用鏈表來實現一個棧,初始化向棧中壓入B、A兩個元素,棧頂head指向A元素。
在某個時刻,線程1試圖將棧頂換成B,但它獲取棧頂的oldValue(為A)后,被線程2中斷了。線程2依次將A、B彈出,然后壓入C、D、A。然后換線程1繼續運行,線程1執行compareAndSet發現head指向的元素確實與oldValue一致,都是A,所以就將head指向B了。但是,注意我標黃的那行代碼,線程2在彈出B的時候,將B的next置為null了,因此在線程1將head指向B后,棧中只剩了一個孤零零的元素B。但按預期來說,棧中應該放的是B → A → D → C。
Node head; head = B; A.next = head; head = A; Thread thread1 = new Thread( ->{ oldValue = head; sleep(3秒); compareAndSet(oldValue, B); } ); Thread thread2 = new Thread( ->{ // 彈出A newHead = head.next; head.next = null; //即A.next = null; head = newHead; // 彈出B newHead = head.next; head.next = null; // 即B.next = null; head = newHead; // 此時head為null // 壓入C head = C; // 壓入D D.next = head; head = D; // 壓入A A.next = D; head = A; } ); thread1.start(); thread2.start();
如有錯誤,敬請指正 >。<