CAS和ABA问题

CAS和ABA问题

    引言:乐观锁和悲观锁的概念

    悲观锁:悲观锁悲观地认为,自己执行操作的过程中一定有人修改过自己操作的值,所以在自己操作之前会加上一把锁,synchronized就是一个悲观锁。

    乐观锁:乐观锁则乐观地认为,当自己执行操作时不会有人修改自己操作的值,所以采用不加锁的机制,只是在操作完成的那一刻发现产生冲突,则会重新执行操作,直到成功为止。CAS算法就是乐观锁的一种实现。

    一、CAS算法

    CAS(CompareAndSet):顾名思义了,十分满足乐观锁的理念,比较后,再set。

    CAS算法通常有三个操作数,一个是内存中的V,一个是自己预计的A(表示操作前内存中的值),和自己要设置的值B,在设置B时,比较A和V,如果两者相等,则直接设置,如果不等,则重试操作。

    二、AtomicInteger源码实现机制

    AtomicInteger是一个原子的Integer,但是AtomicInteger的原子性是如何实现的呢?
    我们来看看源码:

    第一部分,它使用volatile关键字来修饰value,此操作保证了value在各线程之间可见:

private volatile int value;
    第二部分,获取操作,由于value在各线程之间可见,所以无需加锁,直接就可以返回一个值:
public final int get() {
    return value;
}
    第三部分,实现i++操作:
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

    这里就是一个CAS实现了,通过一个死循环,每一次循环,都会去获取当前内存中的value,再对其进行i++操作,最后使用compareAndSet方法比较并设置value的值,如果成功会返回一个成功的值,退出循环,如果失败则继续重试。

    看看compareAndSet方法,是JNI(Java本地方法接口)的一个实现:

public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

    相比起加锁操作,他显然更加高效。

    三、ABA问题

    看上去无懈可击,且十分高效,满足并发的CAS,同样也存在有问题,ABA问题。

    设想一个场景,当线程1对值为A的value正在进行操作,但是,线程2这时进来,将value改为B,并在线程1结束之前,又将value改为了A,看上去一切都没变,这时,线程1完成了CAS操作。但是,如果value是一个链表呢?虽然其链表头没有变,但是不能保证,他的链表内容没有发生变化。

    通常,为了避免ABA问题的隐患,各种CAS实现会使用版本戳。AtomicStampedReference便提供了版本戳的支持,看以下的代码,使用AtomicStampedReference进行CAS,发生ABA问题,CAS会返回false,但是使用AtomicInteger进行CAS,发生ABA,CAS还是会成功:

package concur.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABA {
    
    private static AtomicInteger atomicInt = new AtomicInteger(100);
    private static AtomicStampedReference<Integer> atomicStampedRef = 
            new AtomicStampedReference<Integer>(100, 0);
    
    public static void main(String[] args) throws InterruptedException {
        Thread intT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInt.compareAndSet(100, 101);
                atomicInt.compareAndSet(101, 100);
            }
        });
        
        Thread intT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean c3 = atomicInt.compareAndSet(100, 101);
                System.out.println(c3);        //true
            }
        });
        
        intT1.start();
        intT2.start();
        intT1.join();
        intT2.join();
        
        Thread refT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedRef.compareAndSet(100, 101, 
                        atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
                atomicStampedRef.compareAndSet(101, 100, 
                        atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
            }
        });
        
        Thread refT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedRef.getStamp();
                System.out.println("before sleep : stamp = " + stamp);    // stamp = 0
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1
                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);
                System.out.println(c3);        //false
            }
        });
        
        refT1.start();
        refT2.start();
    }

}

发布了73 篇原创文章 · 获赞 13 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章