由CAS引發的ABA問題該如何解決,JAVA高級工程師不一定了解

在運用CAS做Lock-Free操作時,會產生ABA經典問題。

那麼什麼是ABA問題呢?

線程1準備用CAS將變量的值由A替換為B,在此之前,線程2將變量的值由A替換為C,又由C替換為A,然後線程1執行CAS時發現變量的值仍然為A,所以CAS成功。但實際上這時的現場已經和最初不同了,儘管CAS成功,但可能存在潛藏的問題,例如下面的例子:

在運用CAS做Lock-Free操作時,會產生ABA經典問題。

那麼什麼是ABA問題呢?

線程1準備用CAS將變量的值由A替換為B,在此之前,線程2將變量的值由A替換為C,又由C替換為A,然後線程1執行CAS時發現變量的值仍然為A,所以CAS成功。但實際上這時的現場已經和最初不同了,儘管CAS成功,但可能存在潛藏的問題,例如下面的例子:

由CAS引發的ABA問題該如何解決,JAVA高級工程師不一定了解

現有一個用單向鏈表實現的堆棧,棧頂為A,這時線程T1已經知道A.next為B,然後希望用CAS將棧頂替換為B:

head.compareAndSet(A,B);

在T1執行上面這條指令之前,線程T2介入,將A、B出棧,再入棧D、C、A,此時堆棧結構如下圖,而對象B此時處於遊離狀態:

在運用CAS做Lock-Free操作時,會產生ABA經典問題。

那麼什麼是ABA問題呢?

線程1準備用CAS將變量的值由A替換為B,在此之前,線程2將變量的值由A替換為C,又由C替換為A,然後線程1執行CAS時發現變量的值仍然為A,所以CAS成功。但實際上這時的現場已經和最初不同了,儘管CAS成功,但可能存在潛藏的問題,例如下面的例子:

由CAS引發的ABA問題該如何解決,JAVA高級工程師不一定了解

現有一個用單向鏈表實現的堆棧,棧頂為A,這時線程T1已經知道A.next為B,然後希望用CAS將棧頂替換為B:

head.compareAndSet(A,B);

在T1執行上面這條指令之前,線程T2介入,將A、B出棧,再入棧D、C、A,此時堆棧結構如下圖,而對象B此時處於遊離狀態:

由CAS引發的ABA問題該如何解決,JAVA高級工程師不一定了解

此時輪到線程T1執行CAS操作,檢測發現棧頂仍為A,所以CAS成功,棧頂變為B,但實際上B.next為null,所以此時的情況變為:

在運用CAS做Lock-Free操作時,會產生ABA經典問題。

那麼什麼是ABA問題呢?

線程1準備用CAS將變量的值由A替換為B,在此之前,線程2將變量的值由A替換為C,又由C替換為A,然後線程1執行CAS時發現變量的值仍然為A,所以CAS成功。但實際上這時的現場已經和最初不同了,儘管CAS成功,但可能存在潛藏的問題,例如下面的例子:

由CAS引發的ABA問題該如何解決,JAVA高級工程師不一定了解

現有一個用單向鏈表實現的堆棧,棧頂為A,這時線程T1已經知道A.next為B,然後希望用CAS將棧頂替換為B:

head.compareAndSet(A,B);

在T1執行上面這條指令之前,線程T2介入,將A、B出棧,再入棧D、C、A,此時堆棧結構如下圖,而對象B此時處於遊離狀態:

由CAS引發的ABA問題該如何解決,JAVA高級工程師不一定了解

此時輪到線程T1執行CAS操作,檢測發現棧頂仍為A,所以CAS成功,棧頂變為B,但實際上B.next為null,所以此時的情況變為:

由CAS引發的ABA問題該如何解決,JAVA高級工程師不一定了解

其中堆棧中只有B一個元素,C和D組成的鏈表不再存在於堆棧中,平白無故就把C、D丟掉了。

以上就是由於ABA問題帶來的隱患,各種樂觀鎖的實現中通常都會用版本戳version來對記錄或對象標記,避免併發操作帶來的問題,在Java中,AtomicStampedReference<E>也實現了這個作用,它通過包裝[E,Integer]的元組來對對象標記版本戳stamp,從而避免ABA問題。

相關推薦

推薦中...