CAS以及ABA问题

参考: 

https://www.jianshu.com/p/fb6e91b013cc

https://blog.csdn.net/iteye_4653/article/details/82655171

1.CAS是什么

[1].在AtomicInteger中的compareAndSet方法是这么定义的,即CAS就是Compare-And-Swap(比较并交换)的缩写,另外CAS还有Central Authentication Service(统一认证服务/单点登录)等众多的含,这里讨论的只是Compare-And-Swap这种含义

/**

* Atomically sets the value to the given updated value

* if the current value {@code ==} the expected value.

*

* @param expect the expected value

* @param update the new value

* @return {@code true} if successful. False return indicates that

* the actual value was not equal to the expected value.

*/

public final boolean compareAndSet(int expect, int update) {

    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

}

[2].CAS是乐观锁的一种实现

[3].是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,他通过实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程并且原语的执行必须是连续的,在执行过程中不允许被终端,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致

2.CAS底层原理

[1].源码

①.AtomicInteger.getAndIncrement()方法

/**

* Atomically increments by one the current value.

*

* @return the previous value

*/

public final int getAndIncrement() {

    return unsafe.getAndAddInt(this, valueOffset, 1);

}

②.unsafe和valueOffset的定义

public class AtomicInteger extends Number implements java.io.Serializable {

    private static final long serialVersionUID = 6214790243416807050L;

 

    // setup to use Unsafe.compareAndSwapInt for updates

    private static final Unsafe unsafe = Unsafe.getUnsafe();

    private static final long valueOffset;

 

    static {

        try {

            valueOffset = unsafe.objectFieldOffset

                (AtomicInteger.class.getDeclaredField("value"));

        } catch (Exception ex) { throw new Error(ex); }

    }

 

    private volatile int value;

[3].分析

①.Unsafe,是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据,其方法可以像C的指针一样操作内存,所有的方法都是native修饰的,也就是直接调用操作系统资源执行。unsafe是Unsafe类的一个实例, Unsafe的具体路径为rt.jar/sun/misc/Unsafe.class

②.变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

③.变量value用volatile修饰,保证了多线程之间的内存可见性。

[4].调用原理

[5].Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于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);

  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);

  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

UNSAFE_END

说明:

先想办法拿到变量value在内存中的地址。

通过Atomic::cmpxchg实现比较替换,其中参数x是即将更新的值,参数e是原内存的值。

[6].Atomic::cmpxchg方法

①.如果是Linux的x86,的实现如下:

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {

  int mp = os::is_MP();

  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"

                    : "=a" (exchange_value)

                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)

                    : "cc", "memory");

  return exchange_value;

}

__asm__表示汇编的开始

volatile表示禁止编译器优化

LOCK_IF_MP是个内联函数

②.Window的x86实现如下:

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {

    int mp = os::isMP(); //判断是否是多处理器

    _asm {

        mov edx, dest

        mov ecx, exchange_value

        mov eax, compare_value

        LOCK_IF_MP(mp)

        cmpxchg dword ptr [edx], ecx

    }

}

 

 

// Adding a lock prefix to an instruction on MP machine// VC++ doesn't like the lock prefix to be on a single line// so we can't insert a label after the lock prefix.// By emitting a lock prefix, we can define a label after it.#define LOCK_IF_MP(mp) __asm cmp mp, 0  \

                       __asm je L0      \

                       __asm _emit 0xF0 \

                       __asm L0:

LOCK_IF_MP根据当前系统是否为多核处理器决定是否为cmpxchg指令添加lock前缀。

如果是多处理器,为cmpxchg指令添加lock前缀。

反之,就省略lock前缀。(单处理器会不需要lock前缀提供的内存屏障效果)

intel手册对lock前缀的说明如下:

确保后续指令执行的原子性。

在Pentium及之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低lock前缀指令的执行开销。

禁止该指令与前面和后面的读写指令重排序。

把写缓冲区的所有数据刷新到内存中。

~~~难顶,实在是难顶~~,个人觉得能理解到Unsafe类(第[4]点)已经够面试了

3.CAS缺点

[1].循环时间长,开销大

如果长时间不成功,可能会给CPU带来巨大的开销

[2].只能保证一个共享 变量的原子操作

当多个共享变量操作时,循环CAS就无法保证操作的原子性,这时只能加锁

[3].ABA问题

如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。

package com.w4xj.interview.thread;

 

 

import java.util.concurrent.atomic.AtomicReference;

 

/**

* @Classname ABATest

* @Description TODO

* @Date 2019/5/4 12:25

* @Created by w4xj

*/

public class ABATest {

    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    public static void main(String[] args) {

 

        new Thread(() ->{

            atomicReference.compareAndSet(100,101);

            atomicReference.compareAndSet(101,100);

        },"threadName1").start();

 

 

        new Thread(() ->{

            try {

                Thread.currentThread().sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(atomicReference.compareAndSet(100, 101) + " : " + atomicReference.get());

        },"threadName2").start();

 

    }

}

4.原子引用

[1].AtomicReference实例

package com.w4xj.interview.thread;

 

import lombok.AllArgsConstructor;

import lombok.Getter;

import lombok.ToString;

 

import java.util.concurrent.atomic.AtomicReference;

 

/**

* @Classname AtomicReferenceTest

* @Description TODO

* @Date 2019/5/4 11:49

* @Created by w4xj

*/

public class AtomicReferenceTest {

    public static void main(String[] args) {

        User a = new User("A",12);

        User b = new User("B",14);

 

 

        AtomicReference<User> atomicReference = new AtomicReference<>();

        atomicReference.set(a);

        /*

        打印:

            update result = true, user = User(name=B, age=14)

            update result = false, user = User(name=B, age=14)

         */

        System.out.println("update result = " + atomicReference.compareAndSet(a,b) + ", user = " + atomicReference.get().toString());

        System.out.println("update result = " + atomicReference.compareAndSet(a,b) + ", user = " + atomicReference.get().toString());

    }

}

 

@Getter

@ToString

@AllArgsConstructor

//这里需安装lombok,且引入lombok.jar

class User {

    String name;

    int age;

}

[2].用AtomicStampedReference解决ABA问题

package com.w4xj.interview.thread;

 

import java.util.concurrent.atomic.AtomicStampedReference;

 

/**

* @Classname ABAResolve

* @Description TODO

* @Date 2019/5/4 12:32

* @Created by w4xj

*/

public class ABAResolve {

    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {

        new Thread(() ->{

            int stamp = atomicStampedReference.getStamp();

            System.out.println("第一次:值 = " + atomicStampedReference.getReference() + ", 版本号 = " + stamp);

            try {

                Thread.currentThread().sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("修改结果:" + atomicStampedReference.compareAndSet(100, 101, stamp, ++stamp));

            System.out.println("第二次:值 = " + atomicStampedReference.getReference() + ", 版本号 = " + atomicStampedReference.getStamp());

            System.out.println("修改结果:" + atomicStampedReference.compareAndSet(101, 100, stamp, ++stamp));

            System.out.println("第三次:值 = " + atomicStampedReference.getReference() + ", 版本号 = " + atomicStampedReference.getStamp());

        },"threadName1").start();

 

        new Thread(() ->{

            int stamp = atomicStampedReference.getStamp();

            try {

                Thread.currentThread().sleep(3000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("修改结果:" + atomicStampedReference.compareAndSet(100, 999, stamp, ++stamp));

            System.out.println("第四次:值 = " + atomicStampedReference.getReference() + ", 版本号 = " + atomicStampedReference.getStamp());

        },"threadName2").start();

 

        /*

        打印:

            第一次:值 = 100, 版本号 = 1

            修改结果:true

            第二次:值 = 101, 版本号 = 2

            修改结果:true

            第三次:值 = 100, 版本号 = 3

            修改结果:false

            第四次:值 = 100, 版本号 = 3

         */

 

    }

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值