美文网首页Java 杂谈
J.U.C|打开乐观锁&CAS的大门

J.U.C|打开乐观锁&CAS的大门

作者: 阅历笔记 | 来源:发表于2019-04-15 18:25 被阅读2次

上一章J.U.C|乐观锁为何物我们简单的介绍了乐观锁的大概执行流程,本章将详细介绍下其主要的实现方式CAS。

什么是CAS
  • CAS 全名 Compare and swap (比较并交换)
  • CAS 是乐观锁技术,当多个线程使用CAS技术尝试更新主存中的同一数据时、只能有一个线程更新成功、其它线程更新失败、失败的线程并不会挂起、而是被告知更新失败,失败后可以继续尝试或者报错退出。
  • CAS 无锁算法,基于硬件原语实现的,在不使用锁(没有线程被阻塞)的情况下实现多线程变量之间的同步问题。
  • JDK中的实现,J.U.C包下的原子包就是通过CAS来实现的(整个包就是通过CAS来实现的,由此可见其重要性)。
CAS操作数

算法涉及三个操作数

  • 需要读写的内存值V
  • 进行比较的值A
  • 要写入的新值B
CAS操作流程
CAS流程图.jpg

CAS概念和流程到这基本有个一个清晰的认识了, 上面提到CAS是基于硬件原语来实现下面我们来通过源码简单介绍下(涉及到native方法的一些C/C++实现不做介绍)

CPU指令对CAS的支持

或许我们可能会有这样的疑问,假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和A相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?答案是否定的,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

Unsafe 类介绍

Unsafe类在sun.misc包下面,在Java中CAS的操作都是基于Unsafe类实现的,其内部都是方法都是Native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务, 下面我们来了解下CAS操作相关的主要方法。

//  var1 操作对象, var2 对象内存的偏移量, var4 预期值, var5 表示要设置的值, 下面三方法底层都是通过CAS原子指令操作的。 

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);

Unsafe 类中CAS操作相关的方法基本上就是这三个。下面我们通过AtomicInteger类来看看CAS使用。

Atomic系列

通过前面的分析我们已基本理解了无锁CAS的原理并对Java中的指针类Unsafe类有了比较全面的认识,下面进一步分析CAS在Java中的应用,即并发包中的原子操作类(Atomic系列),从JDK 1.5开始提供了java.util.concurrent.atomic包,在该包中提供了许多基于CAS实现的原子操作类,用法方便,性能高效, 我们主要通过AtomicInteger源码分析下。

AtomicInteger源码主要方法

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // 硬件级别操作,指针类
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // value 变量在 AtomicInteger 中的偏移量
    private static final long valueOffset;

    static {
        try {
            //通过unsafe类的objectFieldOffset()方法,获取value变量在对象内存中的偏移
           //通过该偏移量valueOffset,unsafe类的内部方法可以获取到变量value对其进行取值或赋值操作
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //当前类中封装的int value值, vvolatile 修饰保证内存的可见性
    private volatile int value;

    

    //获取当前值
    public final int get() {
        return value;
    }

    
    // 设置新值回返回旧值,底层调用CAS操作
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

   // 如果当前值等于预期值expect, 则更新当前值为update
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

   // 当前值+1  返回旧值
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    // 当前值 - 1  返回旧值
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

   //当前值 + data 返回旧值
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    // 当前值 + 1 返回新值
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    //当前值 - 1 返回新值
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

通过上述源码我们可以看到其基本原子操作基本都是调用UnSafe中CAS操作来实现的,下面我们来根据自增方法来分析下。

 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

AtomicInteger 中原子自增操作,我们可以看通过unsafe.getAndAddInt(this, valueOffset, 1)方法来实现的。

// var1 操作对象, var2  对象内存中的偏移量, var4 更新值
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        // 自旋
        do {
            // 通过偏移量在对象内存中获取值。
            var5 = this.getIntVolatile(var1, var2);
            // 自旋 通过CAS操作来更新内存中的值
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        // ? 注意返回的的是旧值
        return var5;
    }

写到这对CAS的介绍已经完了, 上面介绍了通过CAS操作给我们带来了很多性能的提升,下面我们来看看其缺点。


CAS缺点.png

ABA问题来源

CAS&ABA问题.png

如上图线程2 内存地址V初次读取的值是0,并且在准备赋值的时候检查到它的值仍然为0,那我们就能说它的值没有被其他线程改变过了吗?
这期间已经被线程3将内存值V从1改为0 ,那么线程2就认为其内存值没有被改变过,这就是CAS操作的漏洞'ABA'问题(个人感觉ABA其实影响不大,只是丢失了其一个过程的记录而已,对整个业务的结果没有产生影响)。

~~~~~~~~上述就是对乐观锁主要实现方式CAS的介绍, 看完希望有收获~~~~~~~~

相关文章

网友评论

    本文标题:J.U.C|打开乐观锁&CAS的大门

    本文链接:https://www.haomeiwen.com/subject/hxepwqtx.html