Java并发编程之 CAS

x33g5p2x  于2021-09-24 转载在 Java  
字(1.3k)|赞(0)|评价(0)|浏览(418)

乐观锁

乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。CAS是乐观锁

CAS

CAS即比较并交换(compare and swap)。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS 操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值(V)与预期原值(A)相匹配,那么处理器会自动将该位置值更新为新值(B)。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置(V)应该包含值(A)。如果包含该值,则将新值(B)放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可”。Java 中sun.misc.Unsafe类提供了硬件级别的原子操作来实CAS。java.util.concurrent包下大量的类都使用了这个Unsafe类的 CAS 操作。
由于Unsafe类涉及到内存操作,不安全,因此它被设计成只能通过Bootstrap ClassLoader来加载:

我们平时写的类都是Application ClassLoader加载的,是不能直接使用Unsafe类的。

CAS的伪代码:

while (true) {
    oldValue = getDefaultValue();
    // 如果CAS成功则跳出循环
    if (CAS(oldValue, newValue)) {
        break;
    }
    // 如果CAS失败则继续重新尝试CAS
}

CAS的底层是lock cmpxchg指令(总线锁,其它 CPU 对内存的读写请求都会被阻塞,直到锁释放。因为锁总线的开销比较大,后来的处理器都采用锁缓存替代锁总线,在无法使用缓存锁的时候会降级使用总线锁),可以保证原子性。

CAS必须借助volatile才能读取到共享变量的最新值来实现“比较并交换”的效果。

CAS的缺陷

ABA问题

并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可能发生了A变B,B又变回A的情况.此时A已经非彼A,数据即使成功修改,也可能有问题。

可以通过AtomicstrapedReference解决ABA问题,它是一个带有标记的原子引用类,通过控制变量值的版本来保证CAS的正确性。

循环时间长开销

自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。

解决方案:设置自旋次数,比如将上面的伪代码进行优化:

for (int i = 0; i < 1000; i++) {
    oldValue = getDefaultValue();
    // 如果CAS成功则跳出循环
    if (CAS(oldValue, newValue)) {
        break;
    }
    // 如果CAS失败则继续重新尝试CAS
}

只能保证一个变量的原子操作

CAS保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS目前无法直接保证操作的原子性的。

可以通过这两个方式解决这个问题:

  • 使用互斥锁来保证原子性
  • 将多个变量封装成对象(变成一个变量),通过AtomicReference来保证原子性。

相关文章