CAS

concurrent包下的类都有下面的实现模式
1、首先,声明变量为volatile
2、然后,使用CAS的原子条件更新来实现线程之间的同步
3、配合使用volatile的写可见性和CAS的原子性来实现线程之间的通信(线程安全)

最近在看 java.util.concurrent.atomic 包下的AtomicInteger源码发现它是利用CAS来实现原子操作、Volatile保证元素的可见性来实现无锁下的线程安全。  决定深入了解一下CAS
MySQL中的MVCC(多版本并发控制)中的乐观锁也是通过CAS机制和版本号实现无锁更新数据的
CAS Campare And Swap 比较和交换
CAS(V,E,N) 的三个参数
V 要修改的对象(有的说是内存位置)
E  expect 期望原值
N  new 要修改为的新值

流程:
1、获取要修改的变量的现有值
2、和期望值比较,是否相同
3、相同的话则将变量的值修改为新值并返回TRUE,否则什么都不做并返回FALSE。

AtomicInteger:   
 /**
     * 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);
    }

另外,还有许多CAS操作时自旋的,即如果操作不成功,会一直重试,直到操作成功为止。


a++的操作是非原子性的,它实际上是两个操作
1、将a的值加一

2、将加一后的值赋给a
在多线程环境下,可能存在下面的问题
1、在不用volatile修饰的情况下
  线程1获取a的值为1,线程2取a的值为1,分别对其进行a++操作,之后a的b不是3人、而是2
2、在用volatile修饰的情况下
  线程1获取a的值为1,线程2获取a的值为1
  线程1在对a加一,但未将加一的值赋给a之前,线程2完成了对a的加一并赋值的操作,此时变量a的值为2
  由于volatile修饰的a保证了可见性,此时线程1中的工作内存中的a值无效,重新从堆内存中获取变量a的值,然后完成将a的值赋值为2
  两个线程分别对变量a进行加一操作后,变量a的值不是期望的3,而是2,这是由于加一和赋值的操作不是原子性导致的,所以并发包内提供的原子性的自增方法
AtomicInteger:   
/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

Unsafe:
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

但是Unsafe的使用是受限的,只有授信的代码才能使用,即JDK库里面的内才可以使用(其帮助Java访问操作系统底层资源,使Java具有底层操作能力,可以提升运行效率)
private static final Unsafe theUnsafe;

// 私有化构造函数

private Unsafe() {
}
//
只有此方法可以获取Unsafe对象
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}

但是可以通过使用反射来获取Unsafe对象

 Class unsafeClass = Unsafe.class;
        try {
            Field f = unsafeClass.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

Unsafe类的方法

 /**
     * CAS
     * @param var1 要修改field的对象
     * @param var2 对象中某field的偏移量
     * @param var4 期望值
     * @param var5 更新值
     * @return true|false
     */
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

获取给定的paramField的内存地址偏移量,这个值对于给定的field是唯一且固定不变的

 public native int arrayBaseOffset(Class<?> var1);


CAS存在的问题 ABA问题
ABA:例如两个线程同时更新同一个数据,线程1操作比较快,先把A修改为了B,然后再修改为了A;
线程2读取到线程1两次修改后的值A和之前的期望值A比较,相同,则认为没有被修改过符合期望,接着修改为新值。
解决方案是引入版本的概念:如A1-B2-A3,这样,另一个线程读取到的A3和期望值A1虽然值相同但是版本不同,则不进行后续的操作。
AtomicInteger中的valueOffset就是解决ABA问题的,这个具体为什么,我还没看懂,
private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的

value是用volatile修饰的,这是非常关键的

CAS如果保证原子性的?
CAS包含C和S两个操作,其能保证原子性是因为CAS是由底层CPU支持的原子操作,其原子性是在硬件层面进行保证的。



原文地址:https://www.cnblogs.com/yangyongjie/p/10654084.html