沉淀再出發:java中的CAS和ABA問題整理


沉淀再出發:java中的CAS和ABA問題整理

一、前言

    在多並發程序設計之中,我們不得不面對並發、互斥、競爭、死鎖、資源搶占等等問題,歸根到底就是讀寫的問題,有了讀寫才有了增刪改查,才有了所有的一切,同樣的也有了誰讀誰寫,這樣的順序和主次問題,於是就有了上鎖,樂觀鎖和悲觀鎖,同步和異步,睡眠和換入換出等問題,歸根到底就是模擬了社會上的分工協作與資源共享和搶占,要理解好這些現象的本質,我們需要更加深刻地進行類比和辨析,要知道這些內容的本質就是內存和CPU之間的故事,有的時候還會有一些外存或者其他緩存。只有我們深刻的理解了這些內容背后的原理和本質,我們才能算是真正的有所感悟,於是我們就需要理解操作系統的保護模式和進程線程的運行機理,需要理解計算機組成的基本原理,明白硬件的基本結構,運行的基本單位,存儲和寄存器等多種電器元件,在理解了軟件和硬件的基礎之上,我們還要有一些編譯原理方面的知識,因為編譯器將我們能看到的程序語言翻譯成了一個個中間代碼直到機器碼,只有明白了這一點才會知道i++,這樣的簡單的代碼其實是有兩三條指令才能完成的,並不是原子操作,明白了這些我們才能夠真正的理解多並發機制。

二、java的CAS原理

  2.1、CAS本質

    java的CAS是Compare And Swap的縮寫,先進行比較再進行交換,是實現java樂觀鎖的一種機制。java.util.concurrent包完全建立在CAS之上的,借助CAS實現了區別於synchronouse同步鎖的一種樂觀鎖。但是這種機制是有一定的問題的,會造成ABA問題,因此需要加時間戳(版本號)這樣的機制,為什么會有悲觀鎖和樂觀鎖呢,本質上如果使用一個進程把一個資源完全鎖住就稱為悲觀鎖,直到這個進程把資源使用完之后才能解鎖,這樣的上鎖會導致其他進程在這段時間之內一直等不到這個內存資源,但是在實際的運行之中很多進程使用內存資源都是用來讀操作的,並不會修改這個內存的內容,基於這樣的實際情況,如果全部使用悲觀鎖,在高並發的環境下必定是非常浪費時間和CPU,內存資源的,因此類似CAS這樣的樂觀鎖就出現了,主要是滿足大部分進程用來讀,少部分進程用來寫這樣的需求的。

    CAS 操作包含三個操作數 —— 內存位置(V)、預期原值(A)和新值(B)。如果內存地址里面的值和A的值是一樣的,那么就將內存里面的值更新成B。CAS是通過無限循環來獲取數據的,若果在第一輪循環中,a線程獲取地址里面的值被b線程修改了,那么a線程需要自旋,到下次循環才有可能機會執行。

    也就是什么意思呢??讓我們想一下,一個內存地址上可以存一個變量,如果不對這個變量進行悲觀鎖(同步)的加鎖方式,我們在面對着很多進程的讀操作的時候肯定是沒問題的,畢竟內存號稱“一次寫入,無數次讀取”的,但是如果是寫操作的時候,我們就要采取一定的辦法保證如果之前已經有進程修改過這塊空間的值了,這次我們的修改就不能繼續下去,不然的話就會造成混亂了,因此我們需要自旋等待下次的操作時機,正是因為這樣的操作機制,我們對內存的使用效率也有了很大的提高。那么我們怎么判斷呢?我們知道內存的地址V,並且采取的輪詢的機制,在這個機制之下,我們首先讀取一次V的值,記為A,之后我們運行CAS算法,這個算法是一個原子操作,為什么不直接把我們需要寫入的數值B寫入內存呢?!原因很簡單,那就是在我們讀取了V的值A之后,假如此時發生了進程切換,這個內存空間被另一個進程修改了,如果此時我們直接寫入B,就把上一個進程的值給取代了,上一個進程就丟失了該信息。因此,我們在下一步需要判斷一下是不是有這種操作發生,如果有的話什么都不做,繼續下一輪的讀V的值A1,繼續比較;如果值沒有變,就姑且認為一切狀態沒發生變化,使用原子操作,比較並且替代這個內存的值。在這種機理之下,就要求這個內存V是唯一的,也就是volatile(易變)的,只在內存中有一份,在其他地方不能被緩存。這就是CAS的本質思想。但是大家覺得有什么不妥的嗎?

  2.2、CAS的問題

    @1、CAS容易造成ABA問題。ABA:一個線程將某一內存地址中的數值A改成了B,接着又改成了A,此時CAS認為是沒有變化,其實是已經變化過了,而這個問題的解決方案可以使用版本號標識,每操作一次version加1。在java5中,已經提供了AtomicStampedReference來解決問題。
    CAS操作容易導致ABA問題,也就是在做i++之間,i可能被多個線程修改過了,只不過回到了最初的值,這時CAS會認為i的值沒有變。i在外面逛了一圈回來,你能保證它沒有做任何壞事,不能!!也許它把b的值減了一下,把c的值加了一下等等,更有甚者如果i是一個對象,這個對象有可能是新創建出來的,i是一個引用情況又如何,所以這里面還是存在着很多問題的,解決ABA問題的方法有很多,可以考慮增加一個修改計數,只有修改計數不變的且i值不變的情況下才做i++,也可以考慮引入版本號,當版本號相同時才做i++操作等,這和事務原子性處理有點類似。
    @2、CAS造成CPU利用率增加。之前說過了CAS里面是一個循環判斷的過程,如果線程一直沒有獲取到狀態,cpu資源會一直被占用。
    @3、會增加程序測試的復雜度,稍不注意就會出現問題。

    @4、只能保證一個共享變量的原子操作。當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合並成一個共享變量來操作。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,可以把多個變量放在一個對象里來進行CAS操作。

  2.3、ABA的解決辦法

    如果一開始位置V得到的舊值是A,當進行賦值操作時再次讀取發現仍然是A,並不能說明變量沒有被其它線程改變過。有可能是其它線程將變量改為了B,后來又改回了A。大部分情況下ABA問題不會影響程序並發的正確性,如果要解決ABA問題,用傳統的互斥同步可能比原子類更高效。可以用CAS在無鎖的情況下實現原子操作,但要明確應用場合,非常簡單的操作且又不想引入鎖可以考慮使用CAS操作,當想要非阻塞地完成某一操作也可以考慮CAS。不推薦在復雜操作中引入CAS,會使程序可讀性變差,且難以測試,同時會出現ABA問題。
    ABA問題的解決辦法:
    1.在變量前面追加版本號:每次變量更新就把版本號加1,則A-B-A就變成1A-2B-3A。
    2.atomic包下的AtomicStampedReference類:其compareAndSet方法首先檢查當前引用是否等於預期引用,並且當前標志是否等於預期標志,如果全部相等,則以原子方式將該引用的該標志的值設置為給定的更新值。

三、具體例子

  3.1、JAVA中CAS的實現

   JAVA中的CAS主要使用的是Unsafe方法,Unsafe的CAS操作主要是基於硬件平台的匯編指令,目前的處理器基本都支持CAS,只不過不同的廠家的實現不一樣罷了。
   Unsafe提供了三個方法用於CAS操作,分別是:

1 public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);
2 public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);  
3 public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);
1     value 表示 需要操作的對象
2     valueOffset 表示 對象(value)的地址的偏移量(通過Unsafe.objectFieldOffset(Field valueField)獲取)
3     expect 表示更新時value的期待值
4     update 表示將要更新的值

    具體過程為每次在執行CAS操作時,線程會根據valueOffset去內存中獲取當前值去跟expect的值做對比如果一致則修改並返回true,如果不一致說明有別的線程也在修改此對象的值,則返回false。
    Unsafe類中compareAndSwapInt的具體實現:

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

  3.2、AtomicInteger類中實現CAS的方法

  1 /*
  2  * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  3  *
  4  *
  5  *
  6  *
  7  *
  8  *
  9  *
 10  *
 11  *
 12  *
 13  *
 14  *
 15  *
 16  *
 17  *
 18  *
 19  *
 20  *
 21  *
 22  *
 23  */
 24 
 25 /*
 26  *
 27  *
 28  *
 29  *
 30  *
 31  * Written by Doug Lea with assistance from members of JCP JSR-166
 32  * Expert Group and released to the public domain, as explained at
 33  * http://creativecommons.org/publicdomain/zero/1.0/
 34  */
 35 
 36 package java.util.concurrent.atomic;
 37 import java.util.function.IntUnaryOperator;
 38 import java.util.function.IntBinaryOperator;
 39 import sun.misc.Unsafe;
 40 
 41 /**
 42  * An {@code int} value that may be updated atomically.  See the
 43  * {@link java.util.concurrent.atomic} package specification for
 44  * description of the properties of atomic variables. An
 45  * {@code AtomicInteger} is used in applications such as atomically
 46  * incremented counters, and cannot be used as a replacement for an
 47  * {@link java.lang.Integer}. However, this class does extend
 48  * {@code Number} to allow uniform access by tools and utilities that
 49  * deal with numerically-based classes.
 50  *
 51  * @since 1.5
 52  * @author Doug Lea
 53 */
 54 public class AtomicInteger extends Number implements java.io.Serializable {
 55     private static final long serialVersionUID = 6214790243416807050L;
 56 
 57     // setup to use Unsafe.compareAndSwapInt for updates
 58     private static final Unsafe unsafe = Unsafe.getUnsafe();
 59     private static final long valueOffset;
 60 
 61     static {
 62         try {
 63             valueOffset = unsafe.objectFieldOffset
 64                 (AtomicInteger.class.getDeclaredField("value"));
 65         } catch (Exception ex) { throw new Error(ex); }
 66     }
 67 
 68     private volatile int value;
 69 
 70     /**
 71      * Creates a new AtomicInteger with the given initial value.
 72      *
 73      * @param initialValue the initial value
 74      */
 75     public AtomicInteger(int initialValue) {
 76         value = initialValue;
 77     }
 78 
 79     /**
 80      * Creates a new AtomicInteger with initial value {@code 0}.
 81      */
 82     public AtomicInteger() {
 83     }
 84 
 85     /**
 86      * Gets the current value.
 87      *
 88      * @return the current value
 89      */
 90     public final int get() {
 91         return value;
 92     }
 93 
 94     /**
 95      * Sets to the given value.
 96      *
 97      * @param newValue the new value
 98      */
 99     public final void set(int newValue) {
100         value = newValue;
101     }
102 
103     /**
104      * Eventually sets to the given value.
105      *
106      * @param newValue the new value
107      * @since 1.6
108      */
109     public final void lazySet(int newValue) {
110         unsafe.putOrderedInt(this, valueOffset, newValue);
111     }
112 
113     /**
114      * Atomically sets to the given value and returns the old value.
115      *
116      * @param newValue the new value
117      * @return the previous value
118      */
119     public final int getAndSet(int newValue) {
120         return unsafe.getAndSetInt(this, valueOffset, newValue);
121     }
122 
123     /**
124      * Atomically sets the value to the given updated value
125      * if the current value {@code ==} the expected value.
126      *
127      * @param expect the expected value
128      * @param update the new value
129      * @return {@code true} if successful. False return indicates that
130      * the actual value was not equal to the expected value.
131      */
132     public final boolean compareAndSet(int expect, int update) {
133         return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
134     }
135 
136     /**
137      * Atomically sets the value to the given updated value
138      * if the current value {@code ==} the expected value.
139      *
140      * <p><a href="package-summary.html#weakCompareAndSet">May fail
141      * spuriously and does not provide ordering guarantees</a>, so is
142      * only rarely an appropriate alternative to {@code compareAndSet}.
143      *
144      * @param expect the expected value
145      * @param update the new value
146      * @return {@code true} if successful
147      */
148     public final boolean weakCompareAndSet(int expect, int update) {
149         return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
150     }
151 
152     /**
153      * Atomically increments by one the current value.
154      *
155      * @return the previous value
156      */
157     public final int getAndIncrement() {
158         return unsafe.getAndAddInt(this, valueOffset, 1);
159     }
160 
161     /**
162      * Atomically decrements by one the current value.
163      *
164      * @return the previous value
165      */
166     public final int getAndDecrement() {
167         return unsafe.getAndAddInt(this, valueOffset, -1);
168     }
169 
170     /**
171      * Atomically adds the given value to the current value.
172      *
173      * @param delta the value to add
174      * @return the previous value
175      */
176     public final int getAndAdd(int delta) {
177         return unsafe.getAndAddInt(this, valueOffset, delta);
178     }
179 
180     /**
181      * Atomically increments by one the current value.
182      *
183      * @return the updated value
184      */
185     public final int incrementAndGet() {
186         return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
187     }
188 
189     /**
190      * Atomically decrements by one the current value.
191      *
192      * @return the updated value
193      */
194     public final int decrementAndGet() {
195         return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
196     }
197 
198     /**
199      * Atomically adds the given value to the current value.
200      *
201      * @param delta the value to add
202      * @return the updated value
203      */
204     public final int addAndGet(int delta) {
205         return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
206     }
207 
208     /**
209      * Atomically updates the current value with the results of
210      * applying the given function, returning the previous value. The
211      * function should be side-effect-free, since it may be re-applied
212      * when attempted updates fail due to contention among threads.
213      *
214      * @param updateFunction a side-effect-free function
215      * @return the previous value
216      * @since 1.8
217      */
218     public final int getAndUpdate(IntUnaryOperator updateFunction) {
219         int prev, next;
220         do {
221             prev = get();
222             next = updateFunction.applyAsInt(prev);
223         } while (!compareAndSet(prev, next));
224         return prev;
225     }
226 
227     /**
228      * Atomically updates the current value with the results of
229      * applying the given function, returning the updated value. The
230      * function should be side-effect-free, since it may be re-applied
231      * when attempted updates fail due to contention among threads.
232      *
233      * @param updateFunction a side-effect-free function
234      * @return the updated value
235      * @since 1.8
236      */
237     public final int updateAndGet(IntUnaryOperator updateFunction) {
238         int prev, next;
239         do {
240             prev = get();
241             next = updateFunction.applyAsInt(prev);
242         } while (!compareAndSet(prev, next));
243         return next;
244     }
245 
246     /**
247      * Atomically updates the current value with the results of
248      * applying the given function to the current and given values,
249      * returning the previous value. The function should be
250      * side-effect-free, since it may be re-applied when attempted
251      * updates fail due to contention among threads.  The function
252      * is applied with the current value as its first argument,
253      * and the given update as the second argument.
254      *
255      * @param x the update value
256      * @param accumulatorFunction a side-effect-free function of two arguments
257      * @return the previous value
258      * @since 1.8
259      */
260     public final int getAndAccumulate(int x,
261                                       IntBinaryOperator accumulatorFunction) {
262         int prev, next;
263         do {
264             prev = get();
265             next = accumulatorFunction.applyAsInt(prev, x);
266         } while (!compareAndSet(prev, next));
267         return prev;
268     }
269 
270     /**
271      * Atomically updates the current value with the results of
272      * applying the given function to the current and given values,
273      * returning the updated value. The function should be
274      * side-effect-free, since it may be re-applied when attempted
275      * updates fail due to contention among threads.  The function
276      * is applied with the current value as its first argument,
277      * and the given update as the second argument.
278      *
279      * @param x the update value
280      * @param accumulatorFunction a side-effect-free function of two arguments
281      * @return the updated value
282      * @since 1.8
283      */
284     public final int accumulateAndGet(int x,
285                                       IntBinaryOperator accumulatorFunction) {
286         int prev, next;
287         do {
288             prev = get();
289             next = accumulatorFunction.applyAsInt(prev, x);
290         } while (!compareAndSet(prev, next));
291         return next;
292     }
293 
294     /**
295      * Returns the String representation of the current value.
296      * @return the String representation of the current value
297      */
298     public String toString() {
299         return Integer.toString(get());
300     }
301 
302     /**
303      * Returns the value of this {@code AtomicInteger} as an {@code int}.
304      */
305     public int intValue() {
306         return get();
307     }
308 
309     /**
310      * Returns the value of this {@code AtomicInteger} as a {@code long}
311      * after a widening primitive conversion.
312      * @jls 5.1.2 Widening Primitive Conversions
313      */
314     public long longValue() {
315         return (long)get();
316     }
317 
318     /**
319      * Returns the value of this {@code AtomicInteger} as a {@code float}
320      * after a widening primitive conversion.
321      * @jls 5.1.2 Widening Primitive Conversions
322      */
323     public float floatValue() {
324         return (float)get();
325     }
326 
327     /**
328      * Returns the value of this {@code AtomicInteger} as a {@code double}
329      * after a widening primitive conversion.
330      * @jls 5.1.2 Widening Primitive Conversions
331      */
332     public double doubleValue() {
333         return (double)get();
334     }
335 
336 }
AtomicInteger代碼
 1 public final int incrementAndGet() {
 2     for (;;) {
 3         int current = get();
 4         int next = current + 1;
 5         if (compareAndSet(current, next))
 6             return next;
 7     }
 8 }
 9 public final int decrementAndGet() {
10     for (;;) {
11         int current = get();
12         int next = current - 1;
13         if (compareAndSet(current, next))
14             return next;
15     }
16 }

   但是上面的操作會造成CAS的ABA問題,基本是這個樣子:

1     進程P1在共享變量中讀到值為A
2     P1被搶占了,進程P2執行
3     P2把共享變量里的值從A改成了B,再改回到A,此時被P1搶占。
4     P1回來看到共享變量里的值沒有被改變,於是繼續執行。

      雖然P1以為變量值沒有改變,繼續執行了,但是這個會引發一些潛在的問題。ABA問題最容易發生在lock free 的算法中的,CAS首當其沖,因為CAS判斷的是指針的地址。如果這個地址被重用了呢,問題就很大了。(地址被重用是很經常發生的,一個內存分配后釋放了,再分配,很有可能還是原來的地址)。比如上述的DeQueue()函數,因為我們要讓head和tail分開,所以我們引入了一個dummy指針給head,當我們做CAS的之前,如果head的那塊內存被回收並被重用了,而重用的內存又被EnQueue()進來了,這會有很大的問題。(內存管理中重用內存基本上是一種很常見的行為)
     具體案例:

 1 package com.cas.aba;
 2 
 3 import java.util.concurrent.atomic.AtomicInteger;
 4 
 5 public class AtomicIntegerTest {
 6     public static AtomicInteger a = new AtomicInteger(1);
 7 
 8     public static void main(String[] args) {
 9         Thread main = new Thread(() -> {
10             System.out.println("操作線程" + Thread.currentThread() + ",初始值 = " + a);
11             // 定義變量 a = 1
12             try {
13                 Thread.sleep(1000);
14                 // 等待1秒 ,以便讓干擾線程執行
15             } catch (InterruptedException e) {
16                 e.printStackTrace();
17             }
18             boolean isCASSuccess = a.compareAndSet(1, 2);
19             // CAS操作
20             System.out.println("操作線程" + Thread.currentThread() + ",CAS操作結果: " + isCASSuccess);
21         }, "主操作線程");
22 
23         Thread other = new Thread(() -> {
24             Thread.yield();
25             // 確保thread-main線程優先執行
26             a.incrementAndGet(); // a 加 1, a + 1 = 1 + 1 = 2
27             System.out.println("操作線程" + Thread.currentThread() + ",【increment】 ,值 = " + a);
28             a.decrementAndGet(); // a 減 1, a - 1 = 2 - 1 = 1
29             System.out.println("操作線程" + Thread.currentThread() + ",【decrement】 ,值 = " + a);
30         }, "干擾線程");
31         main.start();
32         other.start();
33     }
34 }

    可以看到發生了ABA,但是程序還是執行了CAS。

 3.3、AtomicStampedReference類解決ABA問題:

/*
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

/*
 *
 *
 *
 *
 *
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

package java.util.concurrent.atomic;

/**
 * An {@code AtomicStampedReference} maintains an object reference
 * along with an integer "stamp", that can be updated atomically.
 *
 * <p>Implementation note: This implementation maintains stamped
 * references by creating internal objects representing "boxed"
 * [reference, integer] pairs.
 *
 * @since 1.5
 * @author Doug Lea
 * @param <V> The type of object referred to by this reference
 */
public class AtomicStampedReference<V> {

    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair;

    /**
     * Creates a new {@code AtomicStampedReference} with the given
     * initial values.
     *
     * @param initialRef the initial reference
     * @param initialStamp the initial stamp
     */
    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }

    /**
     * Returns the current value of the reference.
     *
     * @return the current value of the reference
     */
    public V getReference() {
        return pair.reference;
    }

    /**
     * Returns the current value of the stamp.
     *
     * @return the current value of the stamp
     */
    public int getStamp() {
        return pair.stamp;
    }

    /**
     * Returns the current values of both the reference and the stamp.
     * Typical usage is {@code int[1] holder; ref = v.get(holder); }.
     *
     * @param stampHolder an array of size of at least one.  On return,
     * {@code stampholder[0]} will hold the value of the stamp.
     * @return the current value of the reference
     */
    public V get(int[] stampHolder) {
        Pair<V> pair = this.pair;
        stampHolder[0] = pair.stamp;
        return pair.reference;
    }

    /**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     * <p><a href="package-summary.html#weakCompareAndSet">May fail
     * spuriously and does not provide ordering guarantees</a>, so is
     * only rarely an appropriate alternative to {@code compareAndSet}.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean weakCompareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
        return compareAndSet(expectedReference, newReference,
                             expectedStamp, newStamp);
    }

    /**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

    /**
     * Unconditionally sets the value of both the reference and stamp.
     *
     * @param newReference the new value for the reference
     * @param newStamp the new value for the stamp
     */
    public void set(V newReference, int newStamp) {
        Pair<V> current = pair;
        if (newReference != current.reference || newStamp != current.stamp)
            this.pair = Pair.of(newReference, newStamp);
    }

    /**
     * Atomically sets the value of the stamp to the given update value
     * if the current reference is {@code ==} to the expected
     * reference.  Any given invocation of this operation may fail
     * (return {@code false}) spuriously, but repeated invocation
     * when the current value holds the expected value and no other
     * thread is also attempting to set the value will eventually
     * succeed.
     *
     * @param expectedReference the expected value of the reference
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean attemptStamp(V expectedReference, int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            (newStamp == current.stamp ||
             casPair(current, Pair.of(expectedReference, newStamp)));
    }

    // Unsafe mechanics

    private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
    private static final long pairOffset =
        objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }

    static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
                                  String field, Class<?> klazz) {
        try {
            return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
        } catch (NoSuchFieldException e) {
            // Convert Exception to corresponding Error
            NoSuchFieldError error = new NoSuchFieldError(field);
            error.initCause(e);
            throw error;
        }
    }
}
AtomicStampedReference代碼

    AtomicStampedReference主要維護包含一個對象引用以及一個可以自動更新的整數"stamp"的pair對象來解決ABA問題。

 1 /**
 2  * 原子更新帶有版本號的引用類型。
 3  * 該類將整數值與引用關聯起來,可用於原子的更數據和數據的版本號。
 4  * 可以解決使用CAS進行原子更新時,可能出現的ABA問題。
 5  */
 6 public class AtomicStampedReference<V> {
 7  
 8     private static class Pair<T> {
 9         final T reference;
10         //最好不要重復的一個數據,決定數據是否能設置成功
11         final int stamp;
12         private Pair(T reference, int stamp) {
13             this.reference = reference;
14             this.stamp = stamp;
15         }
16         //根據reference和stamp來生成一個Pair的實例
17         static <T> Pair<T> of(T reference, int stamp) {
18             return new Pair<T>(reference, stamp);
19         }
20     }
21     //對Value 進行封裝,放入Pair里面
22      private volatile Pair<V> pair;
23  
24     /**
25      * 返回當前的對象
26      */
27     public V getReference() {
28         return pair.reference;
29     }
30  
31     /**
32      * 返回當前的stamp
33      */
34     public int getStamp() {
35         return pair.stamp;
36     }
37  
38     /**
39      * 當當前的引用數據和期望的引用數據相等並且當前stamp和期望的stamp也相等
40      * 並且
41      * (當前的引用數據和新的引用數據相等並且當前stamp和新的stamp也相等
42      * 或者cas操作成功
43      * )
44      * @param 期望(老的)的引用數據
45      * @param 新的引用數據
46      * @param 期望(老的)的stamp值
47      * @param 新的stamp值
48      * @return 
49      */
50     public boolean compareAndSet(V   expectedReference,
51                                  V   newReference,
52                                  int expectedStamp,
53                                  int newStamp) {
54         Pair<V> current = pair;
55         return
56             expectedReference == current.reference &&
57             expectedStamp == current.stamp &&
58             ((newReference == current.reference &&
59               newStamp == current.stamp) ||
60              casPair(current, Pair.of(newReference, newStamp)));
61     }
62  
63     /**
64      * 當前值和期望值比較設置
65      */
66     private boolean casPair(Pair<V> cmp, Pair<V> val) {
67         return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
68     }
69 }

   實驗案例:

 1 package com.cas.aba;
 2 
 3 import java.util.concurrent.atomic.AtomicStampedReference;
 4 
 5 public class AtomicStampedReferenceTest {
 6     private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0); 
 7     public static void main(String[] args){ 
 8         Thread main = new Thread(() -> {
 9             System.out.println("操作線程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference()); 
10             int stamp = atomicStampedRef.getStamp(); 
11             //獲取當前標識別 
12             try { 
13                 Thread.sleep(1000); //等待1秒 ,以便讓干擾線程執行 
14             } catch (InterruptedException e) {
15                 e.printStackTrace();
16             } 
17             boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1); 
18             //此時expectedReference未發生改變,但是stamp已經被修改了,所以CAS失敗 
19             System.out.println("操作線程" + Thread.currentThread() +",CAS操作結果: " + isCASSuccess); 
20          },"主操作線程"); 
21         Thread other = new Thread(() -> { 
22             Thread.yield(); // 確保thread-main 優先執行 
23             atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1); 
24             System.out.println("操作線程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference()); 
25             atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1); 
26             System.out.println("操作線程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference()); 
27         },"干擾線程");
28         main.start(); 
29         other.start(); 
30     }
31 }

 四、總結

     通過對CAS的理解和實驗,我們更加深刻的理解了CAS的樂觀鎖,以及在java中的相應實現和對應ABA補丁的實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM