并发编程-CAS无锁模式及ABA问题

作者: 迦叶_金色的人生_荣耀而又辉煌 | 来源:发表于2020-11-28 07:22 被阅读0次

    上一篇 <<<Volatile的伪共享和重排序
    下一篇 >>>Synchronized锁


    CAS(Compare and Swap):比较并交换
    优势: 非阻塞性,它对死锁问题天生免疫,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销。

    1.Cas 是通过硬件指令实现,保证原子性
    2.Java是通过unsafe jni技术
    3.原子类: AtomicBoolean,AtomicInteger,AtomicLong 等使用 CAS 实现。

    CAS(V,E,N)

    • V表示要更新的变量(主内存)
    • E表示预期值(本地内存)
    • N表示新值

    操作机制:【(本地内存值==主内存预期值)?新值更新:迭代循环等待(也叫自旋)】
    tips: CAS是通过自旋实现的,但不能说CAS就是自旋锁。

    CAS底层源码

    // 调用方法
    atomic.incrementAndGet();
    /*底层源码:*/
    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;
    }
    
    /*以前的写法: */
    public final int incrementAndGet() {  
        for (;;) {  
            //获取当前值  
            int current = get();  
            //设置期望值  
            int next = current + 1;  
            //调用Native方法compareAndSet,执行CAS操作  
            if (compareAndSet(current, next))  
                //成功后才会返回期望值,否则无线循环  
                return next;  
        }  
    }
    

    CAS的ABA问题

    刚开始读取的时候是A,更新的时候也是A,但中间可能会被改为B过了。

    CAS的ABA问题解决办法

    方案1:AtomicStampedReference

    java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。

    /**
     * 使用AtomicStampedReference可获取版本号信息
     * 通过版本号可解决ABA问题
     */
    atomicStampedReference = new AtomicStampedReference(INIT_NUM, 1);
    
    Integer value = (Integer) atomicStampedReference.getReference();
    int stamp = atomicStampedReference.getStamp();
    System.out.println(Thread.currentThread().getName() + " : 当前值为:" + value + " 版本号为:" + stamp);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //(1)第一个参数expectedReference:表示预期值。
    //(2)第二个参数newReference:表示要更新的值。
    //(3)第三个参数expectedStamp:表示预期的时间戳。
    //(4)第四个参数newStamp:表示要更新的时间戳。
    if (atomicStampedReference.compareAndSet(value, UPDATE_NUM, 1, stamp + 1)) {
        System.out.println(Thread.currentThread().getName() + " : 当前值为:" + atomicStampedReference.getReference() + " 版本号为:" + atomicStampedReference.getStamp());
    } else {
        System.out.println("版本号不同,更新失败!");
    }
    

    方案2:利用原子类手写CAS无锁

    /**
     * 定义AtomicInteger  修改为1表示该锁已经被使用该 修改为0表示为被使用
     */
    private volatile AtomicInteger atomicInteger = new AtomicInteger(0);
    private Thread lockCurrentThread;
    
    /**
     * 尝试获取锁
     *
     * @return
     */
    public boolean tryLock() {
        boolean result = atomicInteger.compareAndSet(0, 1);
        if (result) {
            lockCurrentThread = Thread.currentThread();
        }
        return result;
    }
    
    /**
     * 释放锁
     *
     * @return
     */
    public boolean unLock() {
        if (lockCurrentThread != null && lockCurrentThread != Thread.currentThread()) {
            return false;
        }
        return atomicInteger.compareAndSet(1, 0);
    }
    

    Java中Unsafe实现cas无锁机制常用方法

    • 获取unsafe实例
    try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    
    • cas操作
    // o csa对象  offset内存偏移量  expected 预期值  x新值
    public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
    
    • objectFieldOffset获取内存偏移量
    //获取内存偏移量
    public native long objectFieldOffset(Field f);
    
    • park操作
    //park 是挂起当前线程 isAbsolute 为true 代表睡眠一段时自动唤醒 time是纳秒级别
    public native void park(boolean isAbsolute, long time);
    
    • unpark操作
    //unpark 将此线程唤醒
    public native void unpark(Object thread);
    

    相关文章链接:
    多线程基础
    线程安全与解决方案
    锁的深入化
    锁的优化
    Java内存模型(JMM)
    Volatile解决JMM的可见性问题
    Volatile的伪共享和重排序
    Synchronized锁
    Lock锁
    AQS同步器
    Condition
    CountDownLatch同步计数器
    Semaphore信号量
    CyclicBarrier屏障
    线程池
    并发队列
    Callable与Future模式
    Fork/Join框架
    Threadlocal
    Disruptor框架
    如何优化多线程总结

    相关文章

      网友评论

        本文标题:并发编程-CAS无锁模式及ABA问题

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