跳至内容
深圳市捷比信实业有限公司 · 产品选型与采购咨询联系业务人员
点火电阻捷比信 · 专业器件主题站

ABA问题的性质及其解决方案

简介CAS的全名是比较和交换。它是Java同步类的基础。 java.util.concurrent中的同步类基本上使用CAS来实现其原子性。 CAS的原理实际上非常简单,以确保我们的更新符合多线程环境中的期望,或者当一个线程更新对象时,没有其他线程修改该对象。在线程更新对象(或值)之前,请在更新之前保存该值,然后在实际更新该值时传递先前保存的值,并进行比较。如果一致,请对其进行更新,否则它将失败。请注意,CAS是使用Java中的本机方法实现的,该方法利用了系统本身提供的原子操作。那么,使用CAS有什么问题呢?一般来说,如果CAS的设计不完善,可能会发生ABA问题,并且ABA问题可以分为两种类型。让我们首先看一种类型的问题。第一种问题类型我们考虑以下ABA情况:在多线程环境中,线程a从共享地址X读取对象A。在线程a准备更新地址X之前,线程b将地址X中的值修改为B。然后线程b将地址X中的值改回A。最新的线程a在地址X上执行CAS,发现对象A仍存储在X中。对象匹配且CAS成功。在上面的示例中,CAS是成功的,但实际上,此CAS不是原子操作。如果我们要依靠CAS来实现原子操作,则可能存在隐藏的错误。第一种问题的关键在于第2步和第3步。在这两个步骤中,我们可以看到线程b直接替换了内存地址X中的内容。在具有自动GC环境的​​编程语言(例如java)中,情况2、3是不可能的,因为在java中,只要两个对象的地址相同,这意味着两个对象相等。这两个步骤2、3的可能情况是使用C ++这样的编程语言,在这种语言中没有自动GC环境。因为我们可以自己控制对象的生命周期,所以如果我们从列表中删除对象,然后重新分配对象,然后将其添加回列表中,然后根据MRU内存分配算法,这个新对象很有可能删除对象的内存地址与以前相同。这将导致ABA问题。第二种问题如果我们使用的是具有自动GC的编程语言,那么CAS还是有问题吗?考虑以下情况。链接列表中的数据是A-& B-> C。我们要执行CAS操作,用D替换A,以生成链表D-& B-& C。请考虑以下步骤:线程a读取链表的头节点A。线程b删除了链表中的节点B,而链表变为A-> C。线程a执行CAS操作并替换D中的A。最后,我们到达的链表是D-> C,而不是D-> B-> C。问题出在哪儿?通过CAS比较的节点A是否与最新的头节点位于同一节点,所以不在乎节点A的内容在步骤1和3之间是否发生变化。让我们举一个例子:在上面的例子中,我们使用了使用AtomicReference的CAS方法确定对象是否已更改。在CAS b和a之后,我们修改了a的名称,并查看最终输出:[main] INFO com.flydean.aba.ABAUsage-true [main] INFO com.flydean.aba.ABAUsage-true [main] INFO com.flydean.aba.ABAUsage-true这三个CAS的结果都是正确的。说明CAS实际比较的两个对象是否是同一对象,并且不关心内容的变化。第二种类型的问题可能导致某些收集操作不是原子的,因为您不能保证在CAS进程中没有其他节点发送更改。第一种类型的问题解决方法第一种类型的问题在具有自动GC的编程语言中不存在。我们主要看一下如何用C ++等语言解决此问题。根据官方声明,针对第一类问题,大约有四种解决方案:使用中间节点-使用一些不代表任何数据的中间节点来表示某些节点​​被标记为要删除。使用自动GC。使用危险指针-危险指针保存当前线程正在访问的节点的地址。这些危害指标中的节点无法修改或删除。使用只读副本更新(RCU)-在每次更新之前,进行一个副本,并且每个更新都是复制出的新结构。第二种问题的解决方案第二种问题实际上是整个对象集合的CAS问题。一个简单的解决方案是每次进行CAS更新时都添加一个版本号。如果版本号不是预期的版本,则意味着其他线程已更新集合中的某些节点。这次CAS失败了