Java实现CAS的原理

文章目录

  • 1、 什么是CAS
  • 2、CAS的原理
  • 3、CAS的应用场景
  • 4、Java中的CAS实现
  • 5、使用AtomicInteger实现线程安全的计数器
  • 6、CAS实现原子操作的三大问题
    • 6.1、ABA问题
    • 6.2、循环时间长
    • 6.3、只能保证一个共享变量的原子性
  • 7、总结

1、 什么是CAS

CAS(Compare and Swap)是一种并发编程中的技术,用于实现多线程之间的原子操作。它允许你比较一个内存位置的值和一个预期的值,如果相等,则将新值设置到该内存位置,从而实现原子性的修改操作。CAS是乐观锁,线程执行的时候不会加锁,假设没有冲突去完成某项操作,如果因为冲突失败了就重试,最后直到成功为止。

2、CAS的原理

CAS操作主要包括两个步骤:

比较:首先,将内存位置的当前值与预期值进行比较。
交换:如果当前值与预期值相等,就将新值设置到内存位置。
Java实现CAS的原理_第1张图片
前面提到,CAS是一种原子操作。那么Java是怎样来使用CAS的呢?我们知道,在Java中,如果一个方法是native的,那Java就不负责具体实现它,而是交给底层的JVM使用c或者c++去实现。

在Java中,有一个Unsafe类,它在sun.misc包中。它里面是一些native方法,其中就有几个关于CAS的:

boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
boolean compareAndSwapInt(Object o, long offset,int expected,int x);
boolean compareAndSwapLong(Object o, long offset,long expected,long x);

当然,他们都是public native的。

Unsafe中对CAS的实现是C++写的,它的具体实现和操作系统、CPU都有关系。

Linux的X86下主要是通过cmpxchgl这个指令在CPU级完成CAS操作的,但在多处理器情况下必须使用lock指令加锁来完成。当然不同的操作系统和处理器的实现会有所不同,大家可以自行了解。

当然,Unsafe类里面还有其它方法用于不同的用途。比如支持线程挂起和恢复的park和unpark, LockSupport类底层就是调用了这两个方法。还有支持反射操作的allocateInstance()方法。

3、CAS的应用场景

线程安全问题:CAS可以用于解决多线程环境下的数据竞争问题,如计数器、标志位等。
性能优化:CAS避免了使用锁的开销,适用于一些高并发、频繁修改的场景。

4、Java中的CAS实现

Java中提供了java.util.concurrent.atomic包,包含了一系列基于CAS的原子类,如AtomicInteger、AtomicLong等。

5、使用AtomicInteger实现线程安全的计数器

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger value = new AtomicInteger(0);

    public void increment() {
        value.incrementAndGet();
    }

    public int getValue() {
        return value.get();
    }
}

为什么AtomicInteger就是原子性的操作,这里我们以AtomicInteger类的getAndAdd(int delta)方法为例,来看看Java是如何实现原子操作的。

public final int getAndAdd(int delta) {
    return U.getAndAddInt(this, VALUE, delta);
}

这里的U其实就是Unsafe对象;

6、CAS实现原子操作的三大问题

尽管CAS在很多情况下是非常有用的,但它也存在三大经典问题:ABA问题、循环时间长以及只能保证一个共享变量的原子性。

6.1、ABA问题

ABA问题指的是,在CAS操作期间,某个值从A变为B,然后再变回A,而CAS操作的结果可能会错误地判定为成功。这是因为CAS只比较当前值和预期值,不考虑中间是否发生过其他修改。解决ABA问题的方法是引入版本号,即将共享变量的值和版本号一起进行比较和交换。

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAExample {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedRef =
            new AtomicStampedReference<>(1, 0);

        int initialStamp = atomicStampedRef.getStamp();
        int initialValue = atomicStampedRef.getReference();

        // 线程A执行操作,此时线程B修改了值,又恢复原值
        atomicStampedRef.compareAndSet(initialValue, 2, initialStamp, initialStamp + 1);
        
        // 线程C试图修改值,但由于版本号不匹配,CAS失败
        boolean success = atomicStampedRef.compareAndSet(initialValue, 3, initialStamp, initialStamp + 1);
    }
}

6.2、循环时间长

CAS操作是基于循环实现的,即在操作失败时会重试,直至操作成功为止。但当竞争激烈时,可能会导致循环时间较长,浪费CPU资源。解决这个问题的方法是在重试时加入一定的时间限制,避免无限循环。

import java.util.concurrent.atomic.AtomicInteger;

public class LongLoopExample {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger(0);
        
        int expectedValue = 0;
        int newValue = 1;
        int maxRetries = 10;
        
        int retries = 0;
        while (retries < maxRetries) {
            if (counter.compareAndSet(expectedValue, newValue)) {
                // CAS成功,退出循环
                break;
            }
            retries++;
        }
    }
}

6.3、只能保证一个共享变量的原子性

CAS操作只能保证单个共享变量的原子性,不能解决多个共享变量之间的原子操作问题。这意味着如果需要同时修改多个共享变量,CAS可能无法保证这些操作的原子性。

import java.util.concurrent.atomic.AtomicInteger;

public class MultipleVariablesExample {
    public static void main(String[] args) {
        AtomicInteger value1 = new AtomicInteger(0);
        AtomicInteger value2 = new AtomicInteger(0);
        
        value1.incrementAndGet(); // CAS保证了value1的原子操作
        value2.incrementAndGet(); // 但不能保证value1和value2操作的原子性
    }
}

7、总结

虽然CAS存在这三大问题,但在很多情况下仍然是非常有用的,并且在并发编程中发挥着重要的作用。为了解决这些问题,有时需要结合其他技术,如引入版本号来解决ABA问题,或者使用锁来处理多个共享变量之间的复杂原子操作。

你可能感兴趣的:(总结,CAS,Java,原子性,并发编程)