AtomicStampedReference源码解析

AtomicStampedReference源码解析

AtomicStampedReference类介绍

AtomicReference或者说CAS存在一个比较常见的问题,就是ABA问题。因此,JDK提供了AtomicStampedReference来解决CAS中的ABA问题。

类图

AtomicStampedReference类图

主要属性

1
2
3
private volatile Pair<V> pair;
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

pair:内部类对象,主要用来持有reference和stamp

UNSAFE:JDK提供的实现CAS算法的类

pairOffset:pair域在整个类中的内存偏移量

主要方法

AtomicStampedReference(V,int)

1
2
3
4
5
6
7
8
9
10
/**
* 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);
}

调用pair的of方法,生成一个新的pair对象。

public V getReference()

1
2
3
4
5
6
7
8
/**
* Returns the current value of the reference.
*
* @return the current value of the reference
*/
public V getReference() {
return pair.reference;
}

返回AtomicStampedReference持有的引用。

public int getStamp()

1
2
3
4
5
6
7
8
/**
* Returns the current value of the stamp.
*
* @return the current value of the stamp
*/
public int getStamp() {
return pair.stamp;
}

返回当前Reference被修改的次数

get(int[])

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 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;
}

获取当前AtomicStampedReference持有的引用Reference和stamp

weakCompareAndSet(V,V,int,int)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 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);
}

从父类的注释上看,这个方法的本意是,调用weakCompareAndSet方法时不能保证指令重排的发生,因此,这个方法有时候会毫无理由地失败。

但是从实现上来看,该方法就是compareAndSet方法。

compareAndSet(V,V,int,int)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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)));
}

如果当前的引用current.reference等于expectedReference,当前的“时间戳”等于期望的“时间戳”expectedStamp,那么就把当前的引用和“时间戳”全部更新为新的引用和“时间戳”。

set(V,int)

1
2
3
4
5
6
7
8
9
10
11
/**
* 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);
}

将当前的引用和“时间戳”设置为新的引用和“时间戳”。

attemptStamp(V,int)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 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)));
}

“尝试”更新当前的引用和“时间戳”。调用的casPair方法可能失败。

casPair(Pair,Pair)

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

如果当前的pair和cmp相等,则将pair替换为val。

objectFieldOffset(Unsafe,String,Class)

1
2
3
4
5
6
7
8
9
10
11
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;
}
}

获取字段pair在整个类中的内存偏移量。

应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {

String str1 = "aaa";
String str2 = "bbb";
AtomicStampedReference<String> reference = new AtomicStampedReference<String>(str1,1);
reference.compareAndSet(str1,str2,reference.getStamp(),reference.getStamp()+1);
System.out.println("reference.getReference() = " + reference.getReference());

boolean b = reference.attemptStamp(str2, reference.getStamp() + 1);
System.out.println("b: "+b);
System.out.println("reference.getStamp() = "+reference.getStamp());

boolean c = reference.weakCompareAndSet(str2,"ccc",4, reference.getStamp()+1);
System.out.println("reference.getReference() = "+reference.getReference());
System.out.println("c = " + c);
}