JUC 并發包 中也提供了三個原子應用類型的操作類:
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference
有時候原子操作不一定是基本資料類型,而是 BigDecimal 類型,是以需要用到這些類。
AtomicReference 在聲明的時候需要指定一個泛型
AtomicReference<BigDecimal>
,和 AtomicInteger 是方式不一樣,底層調用的是
Unsafe#compareAndSwapObject
方法。
下面是一個使用例子:
// DecimalAccount 是一個自定義接口,隻有 getBalance 和 withdraw 抽象方法。
class DecimalAccountSafeCas implements DecimalAccount {
AtomicReference<BigDecimal> ref;
public DecimalAccountSafeCas(BigDecimal balance) {
ref = new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return ref.get();
}
@Override
public void withdraw(BigDecimal amount) {
while (true) {
BigDecimal prev = ref.get();
BigDecimal next = prev.subtract(amount);
if (ref.compareAndSet(prev, next)) {
break;
}
}
}
}
AtomicReference 就是修改引用所指向的對象執行個體。
用來解決 ABA 問題,并且可以擷取到值被更改了多少次。
下面是一個例子:
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
// 擷取值 A
String prev = ref.getReference();
// 擷取版本号
int stamp = ref.getStamp();
log.debug("版本 {}", stamp);
// 如果中間有其它線程幹擾,發生了 ABA 現象
other();
sleep(1);
// 嘗試改為 C
log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}
private static void other() {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
ref.getStamp(), ref.getStamp() + 1));
log.debug("更新版本為 {}", ref.getStamp());
}, "t1").start();
sleep(0.5);
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
ref.getStamp(), ref.getStamp() + 1));
log.debug("更新版本為 {}", ref.getStamp());
}, "t2").start();
}
下面是針對 compareAndSet 方法的源碼分析:
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
// pair 就是目前的值
Pair<V> current = pair;
return
// 舊資料與目前資料是否相等。如果是基礎資料類型,則比較值;如果是引用資料類型,則判斷的是記憶體位址。
expectedReference == current.reference &&
// 舊版本号是否與目前的版本号相等。
expectedStamp == current.stamp &&
// 上面這兩個條件,有一個不成立,就說明資料已經被别的線程已經修改過了。
// 下面這兩個條件就是為了防止做無用的 CAS 操作;也就是說,新資料、新版本号和目前的資料、版本号都是一樣的,也沒有别要再做 CAS 操作了。
((newReference == current.reference &&
newStamp == current.stamp) ||
// 上面這四個條件,就是為了防止無用的 CAS 操作,提高性能的;而下面這個方法才是主要做 CAS 操作的方法。
casPair(current, Pair.of(newReference, newStamp)));
}
private boolean casPair(Pair<V> cmp, Pair<V> val) {
// 多線程環境下,該方法有可能也會失敗。
// 這裡就是再修改引用本身。
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
AtomicStampedReference 的實作完全依賴它的私有靜态内部類 Pair,Pair 有兩個屬性:reference 用來儲存資料,stamp 用啦儲存版本。
class GarbageBag {
String desc;
public GarbageBag(String desc) {
this.desc = desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return super.toString() + " " + desc;
}
}
public class TestABAAtomicMarkableReference {
public static void main(String[] args) throws InterruptedException {
GarbageBag bag = new GarbageBag("裝滿了垃圾");
// 參數2 mark 可以看作一個标記,表示垃圾袋滿了
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
log.debug("主線程 start...");
GarbageBag prev = ref.getReference();
log.debug(prev.toString());
new Thread(() -> {
log.debug("打掃衛生的線程 start...");
bag.setDesc("空垃圾袋");
while (!ref.compareAndSet(bag, bag, true, false)) {}
log.debug(bag.toString());
}).start();
Thread.sleep(1000);
log.debug("主線程想換一隻新垃圾袋?");
boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);
log.debug("換了麼?" + success);
log.debug(ref.getReference().toString());
}
}