原子变量的这种CAS看起来很爽,但是有可能会导致ABA问题。
什么是ABA问题?
简单点就是,3个线程对某个原子变量val inte = new AutoicInteger(1)进行操作
- A线程要做inte.compareAndSet(1,2)
- B线程要做inte.compareAndSet(1,3)
- C线程要做inte.compareAndSet(3,1)
- A线程没抢过B,C两个线程,且BC两个线程中,B线程先执行的,然后是C线程,当C线程执行完后inte的内存地址已发生过两次偏移,新的地址值是1。
- A线程要做的是将inte的值变成2,当它从地址中进行读取的时候发现值是1正确,所以会赋值一个2,但是这里的inte地址已经不是当初新创建的那个地址了(因为内存地址发生过两次偏移);
ABA问题会导致的后果
ABA问题在只需要比对数值的这种情况下是没有问题的,例如查询库存量,然后进行相应的删除或修改。
但在堆栈操作中就会出现问题了如下(假设有如下场景):
- 一个用单向链表实现的栈结构,栈顶元素为A。
- 这时线程1已经知道A.next为B,然后希望用CAS将栈顶替换为B;
- 在线程1执行上面这条指令之前,线程2先得到执行,并且线程2将A、B两个元素依次出栈,再将D、C、A元素压入栈中。对象B此时处于游离状态,栈结构如下:
- 此时轮到线程1进行CAS操作,检测发现栈顶仍然是A,所以CAS成功,栈顶元素变为B,但实际上B.next为null,所以此时的情况变为:栈中只有B一个元素,C和D不再存在于栈中,这样C和D元素就丢了。
(本文章虽然采用的代码为scala代码,但java代码与Scala代码可以互相转换,且本质上两者所阐述的东西都是一致的)