CAS的ABA问题
CAS在使用时,关键要点是判定当前内存的值是否和寄存器中的值一样,是一样的,就进行修改,不一样就啥都不做。
但是可能存在这种情况:执行CAS之前,一个数值本来是0,一个线程把0改为100,然后又把100改为0,把“0”看作“A”,把“100”看作“B”,这就是ABA问题。一般来说,这种情况不会有什么问题,但是极端情况下,可能会有bug。
ABA问题可能存在问题的情况:
假如有一个链表
class Node {int value;Node next;Node(int value) {this.value = value;this.next = null;}
}
然后有三个线程对链表进行操作
import java.util.concurrent.atomic.AtomicReference;class Main {static AtomicReference<Node> head = new AtomicReference<>(new Node(1));public static void main(String[] args) throws InterruptedException {// 线程1:准备将节点1替换为节点2Thread thread1 = new Thread(() -> {Node oldHead = head.get();Node newHead = new Node(2);newHead.next = oldHead;head.compareAndSet(oldHead, newHead);});// 线程2:准备将节点2替换为节点1Thread thread2 = new Thread(() -> {Node currentHead = head.get();Node newHead = currentHead.next;head.compareAndSet(currentHead, newHead);});// 线程3:尝试删除节点1Thread thread3 = new Thread(() -> {Node oldHead = head.get();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}boolean result = head.compareAndSet(oldHead, null);if (result) {System.out.println("节点删除成功");} else {System.out.println("节点删除失败");}});thread1.start();thread2.start();thread3.start();thread1.join();thread2.join();thread3.join();}
}
- 线程 3 在经过一段延迟(这里是模拟线程调度的时间差)后,获取到链表头节点的值为节点 1,它期望这个节点 1 是初始的那个节点 1,但实际上这个节点 1 已经不是原来的节点 1 了,在这期间发生了从 1 到 2 再回到 1 的变化。然而,CAS 操作在比较时发现当前值(这个新的节点 1)和预期值(初始的节点 1)相等,所以可能会错误地执行删除操作,这就产生了 ABA 问题。
ABA的解决方案
1.约定数据变化只能是单向变化,不能是双向的。
2.对于必须双向变化的情况,可以引入版本号,版本号是只能增加不能减少。