cas(Conmpare And Swap)与aba

cas(Conmpare And Swap)比较和交换算法
例子:对int i = 0;进行i++操作。
1.读取数据i为0
2.对0自增后得到数值1
3.比较i的值是否还是原来的0
4.是的话把i赋值为1
类似于数据库乐观锁update myTable set i = i + 1 where i = 0;所以3和4步必须要同时执行,要有原子性。

比如AtomicInteger就使用了cas算法,它执行update myTable set i = i + 1 where i = 0;时,底层用了汇编命令lock cmpxchg将0改成1,
cmpxchg(compare and exchange)是指使用cas算法把0改成1。
lock是保证cmpxchg命令操作内存时,只能同时有一个cpu进行操作,单cpu保证了顺序执行,实现了原子性。

cas可能会发生aba情况,aba情况是指线程a把0改成1过程中,可能有线程b把0改成1又改回了0。

某些业务场景下,出现aba情况可能会导致一些问题。
比如有一个售票系统规定当卖出第100张票时,会在页面上弹出"票已售完"的提示框,用AtomicInteger做累加计数,
用户1购买时读取当前票数为99张,此时用户2去购买又读取到为99张,然后用户1购买成功,此时页面弹框"票已售完",
突然某种原因导致购买失败(如银行交易返回错误等等)发生退票,用户2此时进行购买,对比发现还是99张票于是购买成功,页面又弹一个框"票已售完"。
如果多个用户同时发生以上情况,页面上可能会弹出好几个框,如果把弹框换成其他操作呢,多次操作也许会导致其他严重的问题发生。

解决aba的办法是可以加一个版本号,先读取版本号,进行更新时判断版本号没变才执行:
int currentVersion = getCurrentVersion();
update myTable set i = i + 1 where i = 0 and version = currentVersion;

以下例子证明AtomicInteger存在aba问题,AtomicStampedReference使用了版本号解决了aba问题:
public class Aba {
    private static AtomicInteger atomicInteger = new AtomicInteger(1);
    private static AtomicStampedReference<Integer> atomicStampedReference =
            new AtomicStampedReference<Integer>(1, 0);

    public static void main(String[] args) throws Exception {
        //获取当前数值,此时num = 1
        int num = atomicInteger.intValue();
        new Thread(new Runnable() {
            public void run() {
                //将1改成2
                atomicInteger.compareAndSet(1, 2);
                //将2改回1
                atomicInteger.compareAndSet(2, 1);
            }
        }).start();

        Thread.sleep(100);
        //将1改成2
        boolean isSuc = atomicInteger.compareAndSet(num, num + 1);
        //更改成功,输出true
        System.out.println(isSuc);


        //获取当前数值,此时num = 1
        num = atomicStampedReference.getReference().intValue();
        //获取当前版本号
        int stamp = atomicStampedReference.getStamp();
        //另有一个线程把1改成2后,再把2改回1
        new Thread(new Runnable() {
            public void run() {
                //将1改成2,同时把版本号加1
                atomicStampedReference.compareAndSet(1, 2,
                        atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                //将2改成1,同时把版本号加1
                atomicStampedReference.compareAndSet(2, 1,
                        atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            }
        }).start();

        Thread.sleep(100);
        //将1改成2,同时把版本号加1,但此时真正的版本号已经由stamp变成了(stamp+2)了,所以会返回失败
        isSuc = atomicStampedReference.compareAndSet(num, num + 1, stamp, stamp + 1);
        //更改失败,输出false
        System.out.println(isSuc);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值