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());
}
}