CAS ABA问题
在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁(后面的章节还会谈到锁)。
锁机制存在以下问题:
(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
CAS 操作
上面的乐观锁用到的机制就是CAS,Compare and Swap。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
非阻塞算法 (nonblocking algorithms)
一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。
拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。
private volatile int value;
首先毫无以为,在没有锁的机制下可能需要借助volatile原语,保证线程间的数据是可见的(共享的)。这样才获取变量的值的时候才能直接读取。
public final int get() {
return value;
}
然后来看看++i是怎么做到的。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
而compareAndSet利用JNI来完成CPU指令的操作。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。
而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。
CAS看起来很爽,但是会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。
首先要知道什么叫CAS(compare and swap):在jdk里面可以由UnSafe提供一个针对于volatile变量的操作,其原理是直接调用的CPU的CAS动作
举个例子:
我们经常会有这样的操作
if(a==b)
a = b+1;
在多线程的环境下,如果在执行完a==b之后,a发生了改变,我们就达不到预期的结果了
所以,就有了cas操作
具体你可以看看JDK的AtomicInteger,就提供了这样的cas操作compareAndSet
有了CAS,对于程序而言还不够
因为compareAndSet只是做一个尝试,如果失败了怎么办?我们还是想要做到a=b+1的结果
所以有了这样的代码
while(true)
{
if(a.compareAndSet(expect,update))
……
else
continue;
}
通过这样的方法,我们就得到了a=b+1的效果(在没上锁的情况下)
再下一步,进入你的问题了,ABA问题在这种编码下就出现了
假如你的一次else之后,再次a.compareAndSet(expect,update)之前
a改变了两次,第一次改为其他值,第二次改回来了
这样称之为ABA问题
分享到:
相关推荐
CAS下ABA问题及优化方案 CAS(Compare And Set)是一种常见的乐观锁机制,用于降低读写锁冲突,保证数据一致性。但是在极端情况下,CAS 乐观锁机制可能会出现 ABA 问题。本文将从ABA问题的定义、出现原因、优化方案...
3. **ABA问题**:在某些情况下,一个线程读取到的值A,在其他线程中被修改为B,然后再变回A,CAS会认为值没有变化,从而可能导致错误的结果。为了解决这个问题,可以使用版本号或者“戳”来记录变量的状态,如Java中...
总的来说,虽然CAS提供了一种高效的无锁同步机制,但需要开发者注意ABA问题可能带来的潜在风险,并根据具体应用场景选择合适的解决方案。在设计并发程序时,理解CAS的工作原理以及它可能引发的问题是至关重要的,...
CAS(比较并交换)一个小demo import java.util.concurrent.atomic.AtomicInteger; public class CasDemo { public static void main(String[] args) { //默认初始值为5,也就是主存中的值为5 AtomicInteger ...
Java中的ABA问题是指在使用Compare-And-Swap(CAS)操作时可能出现的一种问题。该问题的产生是由于CAS操作的原子性和并发环境中的线程执行顺序的不确定性。ABA问题的出现可能会导致程序的不正确执行和数据的不一致。...
然而,CAS虽然解决了原子性问题,但并不能避免ABA问题。ABA问题是指在并发环境下,一个值从A变到B,然后再变回A,而CAS可能只检查值是否还是A,却忽略了它曾经变为B的过程。这个比喻中,你手中的手提箱虽然看起来...
CAS操作可能会遇到ABA问题(即在两次比较之间,一个值可能被多次修改回到初始状态),这时可以考虑使用带有版本号的CAS或其他替代方案。 #### 七、结论 CAS作为多线程环境中不可或缺的一种同步机制,通过硬件层面...
问题 CopyOnWrite容器 RingBuffer 可重入锁 & 不可重入锁 互斥锁 & 共享锁 死锁 操作系统 计算机原理 CPU 多级缓存 进程 线程 协程 Linux 设计模式 设计模式的六大原则 23种常见设计模式 应用场景 单例模式 责任链...
2. **避免ABA问题**:如果在CAS过程中,旧值A被修改为其他值,然后再变回A,CAS操作会成功,但可能隐藏了错误。解决方法可以引入版本号或者使用更高级的同步原语。 3. **队列扩容**:如果队列接近满,需要动态扩容...
- **ABA问题**:当一个值从A变到B,然后再变回A,CAS会认为未发生改变,但实际上值已经被修改过。这是一个潜在的数据一致性风险。 5. **ABA问题的解决** Java通过`AtomicStampedReference`或`...
但是,它也有一定的局限性,比如ABA问题(值从A变为B再变回A,但CAS仍然认为没有变化),以及可能导致循环等待(自旋锁)的高CPU占用。因此,在实际使用中,开发者需要根据场景选择合适的同步策略,如使用带有版本号...
这种情况可能导致循环,也就是所谓的"ABA问题",即一个值被改变,然后又变回原值,但中间可能发生了其他操作,使得系统状态发生不可预知的变化。 为了解决ABA问题,Java提供了一些特殊的原子类,如`...
此外,ABA问题虽然可以被`AtomicStampedReference`缓解,但并不能完全解决,有时候还需要额外的逻辑来处理。 总的来说,理解并熟练使用CAS是Java并发编程中的一项重要技能,它能帮助我们编写出高效且线程安全的代码...
- **ABA问题**:在CAS操作过程中,如果某个值由A变为B再变回A,其他线程可能会误以为该值未被更改。 - **解决方案**: 1. **版本号**:为每个变量添加一个版本号,每次更新变量时都增加版本号,以此区分相同的值。 ...
首先是ABA问题,即在CAS操作中,如果一个变量在被读取后、在被写入新值前,其值被修改为另一个值然后再改回原值,CAS操作可能会认为这个变量没有被修改过,导致数据不一致的问题。为了解决ABA问题,Java提供了...
本文主要研究了茶树ABA信号转导家族基因的鉴定与功能分析,包括CsPYLs/PP2CAs/SnRK2s三个基因家族的表达模式、在逆境胁迫中的表达模式、相互之间的互作关系等。 研究结果表明,茶树中存在15个CsPYLs基因,8个CsPP2...
1. **ABA问题**:CAS操作可能会遇到ABA问题,即线程A读取到的值为A,线程B将该值修改为B再改回A,此时线程A再次尝试修改时会成功,但实际上中间发生过修改。 2. **循环时间开销大**:CAS操作通常需要在一个循环中多...
- ABA问题:如果一个值从A变为B,然后又变回A,即使这个过程中有变化,但CAS依然会认为没有改变。解决这个问题通常需要配合使用版本号或时间戳。 - 循环等待(自旋):当多个线程因冲突而反复尝试时,可能导致CPU...