juc之CAS

一、CAS

CAS有3个操作数,位置内存值N,旧的预期值A,要修改的更新值B,当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来

在这里插入图片描述

	private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

	//this:当前对象 valueOffset:当前对象在内存中的偏移量 expect:期望值 update:更新后的值
	public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

1.1Unsafe类

是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

注意:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底昃资源执行相应任务

1.2CAS如何保证原子操作的

CAS的全称为Compare-And-Swap,它是一条CPU并发原语。
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
AtomicInteger类主要利用CAS(compare and swap) + volatile和 native方法来保证原子操作,从而避免 synchronized的高开销,执行效率大为提升。

	public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

volatile不能保证操作的原子性,这就会导致多个线程在修改同一个变量时,由于缓存一致性协议会导致修改失效

public class VolatileNoAtomicDemo
{
    public static void main(String[] args) throws InterruptedException
    {
        MyNumber myNumber = new MyNumber();

        for (int i = 1; i <=10; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "\t" + myNumber.number);
    }
}

class MyNumber
{
    volatile int number = 0;

    public void addPlusPlus()
    {
        number++;
    }
}

使用AtomicInteger是如果当前修改失败会进行重试

@Slf4j
public class CASTest {
    
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger nu = new AtomicInteger();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    nu.getAndIncrement();

                }
            }).start();
        }

        Thread.sleep(1000);
        log.info("num的值为{}",nu);

    }
}

二.底层实现

Unsafe.java类

public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

unsafe.cpp

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  //拿到变量value在内存中的地址,根据偏移量valueOffsht,计算 value 的地址
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  //调用Atomic中的函数cmpxchg来进行比较交换,其中参数x是即将更新的值,参数e是原内存的值
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

atomic.cpp

unsigned Atomic::cmpxchg(unsigned int exchange_value,
                         volatile unsigned int* dest, unsigned int compare_value) {
  assert(sizeof(unsigned int) == sizeof(jint), "more work to do");
//  根据操作系统类型调用不同平台下的重载函数,这个在预编译期间编译器会决定调用哪个平台下的重载函数
  return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest,
                                       (jint)compare_value);
}

windows系统atomic_windows_x86.inline.hpp

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
  //三个move指令表示的是将后面的值移动到前面的寄存器上
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    //CPU原语级别,CPU触发
    LOCK_IF_MP(mp)
    //比较并交换指令
	//cmpxchg:即“比较并交换"指令
	//dword:全称是double word表示两个字,一共四个字节
	//ptr:全称是pointer,与前面的dword连起来使用,表明访问的内存单元是一个双字单元
	//将eax寄存器中的值(compare_value)与[edx]双字内存单元中的值进行对比,
	//如果相同,则将ecx寄存器中的值( exchange_value)存入[edx]内存单元中
    cmpxchg dword ptr [edx], ecx
  }
} 

三、CAS的缺点

3.1 导致cpu空转

如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

3.2 ABA问题

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
 
比如说一个线程A从内存位置V中取出num=1,这时候另一个线程B也从内存中取出num=1,并且线程B进行了一些操作将值变成了2,
然后线程B又将V位置的数据变成1,这时候线程A进行CAS操作发现内存中仍然是1,然后线程A操作成功。

解决:加版本号,不仅比较当前值还要比较当前版本(AtomicStampedReference)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值