CAS存在的问题以及解决方案

CAS 存在三个经典问题,ABA 问题、自旋开销大、只能操作一个变量等.

ABA 问题指的是,一个值原来是 A,后来被改为 B,再后来又被改回 A,这时 CAS 会误认为这个值没有发生变化。

线程 1:CAS(A → B),修改变量 A → B
线程 2:CAS(B → A),变量又变回 A
线程 3:CAS(A → C),CAS 成功,但实际数据已被修改过!

可以使用版本号/时间戳的方式来解决 ABA 问题。

比如说,每次变量更新时,不仅更新变量的值,还更新一个版本号。CAS 操作时,不仅比较变量的值,还比较版本号。

class OptimisticLockExample {
    private int version;
    private int value;

    public synchronized boolean updateValue(int newValue, int currentVersion) {
        if (this.version == currentVersion) {
            this.value = newValue;
            this.version++;
            return true;
        }
        return false;
    }
}

Java 的 AtomicStampedReference 就增加了版本号,它会同时检查引用值和 stamp 是否都相等。

示例:

class ABAFix {
    private static AtomicStampedReference<String> ref = new AtomicStampedReference<>("100", 1);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = ref.getStamp();
            ref.compareAndSet("100", "200", stamp, stamp + 1);
            ref.compareAndSet("200", "100", ref.getStamp(), ref.getStamp() + 1);
        }).start();

        new Thread(() -> {
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            int stamp = ref.getStamp();
            System.out.println("CAS 结果:" + ref.compareAndSet("100", "300", stamp, stamp + 1));
        }).start();
    }
}
自旋开销大怎么办?

CAS 失败时会不断自旋重试,如果一直不成功,会给 CPU 带来非常大的执行开销。

可以加一个自旋次数的限制,超过一定次数,就切换到 synchronized 挂起线程。

int MAX_RETRIES = 10;
int retries = 0;
while (!atomicInt.compareAndSet(expect, update)) {
    retries++;
    if (retries > MAX_RETRIES) {
        synchronized (this) { // 超过次数,使用 synchronized 处理
            if (atomicInt.get() == expect) {
                atomicInt.set(update);
            }
        }
        break;
    }
}
多个变量同时更新问题?

可以将多个变量封装为一个对象,使用 AtomicReference 进行 CAS 更新。

class Account {
    static class Balance {
        final int money;
        final int points;

        Balance(int money, int points) {
            this.money = money;
            this.points = points;
        }
    }

    private AtomicReference<Balance> balance = new AtomicReference<>(new Balance(100, 10));

    public void update(int newMoney, int newPoints) {
        Balance oldBalance, newBalance;
        do {
            oldBalance = balance.get();
            newBalance = new Balance(newMoney, newPoints);
        } while (!balance.compareAndSet(oldBalance, newBalance));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值