JUC原子引用与ABA问题

JUC原子引用

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference
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;
 }
 }
 }
}

ABA 问题及解决

ABA 问题

static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
 log.debug("main start...");
 // 获取值 A
 // 这个共享变量被它线程修改过?
 String prev = ref.get();
 other();
 sleep(1);
 // 尝试改为 C
 log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
}
private static void other() {
 new Thread(() -> {
 log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
 }, "t1").start();
 sleep(0.5);
 new Thread(() -> {
 log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
 }, "t2").start();
}

主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程希望:
只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号

AtomicStampedReference

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

AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference

在这里插入图片描述

AtomicMarkableReference

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值