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

上一篇 << 下一篇 >>>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);

相关文章链接:
<<<多线程基础
<<<线程安全与解决方案
<<<锁的深入化
<<<锁的优化
<< << << << << << << << << << <<<线程池
<<<并发队列
<< << << << <<<如何优化多线程总结

你可能感兴趣的:(并发编程-CAS无锁模式及ABA问题)