Java并发:CAS、及CAS中ABA问题解决方案


【1】CAS方法:CompareAndSwap

1、乐观锁的使用的机制就是CAS。 
在CAS方法中,CAS有三个操作数,内存值V,旧的预期值E,要修改的新值U。当且仅当预期值E和内存值V相等时,将内存值V修改为U,否则什么都不做。

2、非阻塞算法(nonblocking algorithms):一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。 
(1)非阻塞算法简介:https://www.ibm.com/developerworks/cn/java/j-jtp04186/ 
(2)非阻塞算法通常叫作乐观算法,因为它们继续操作的假设是不会有干扰。如果发现干扰,就会回退并重试。

3、CAS方法 
(1)CompareAndSwap()就使用了非阻塞算法来代替锁定。 
(2)举例:AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。 
在没有锁机制的情况下,要保证线程间的数据是可见的,就会常常用到volatile原语了。 
private volatile int value;

可使用如下方法读取内存变量值value:

public final int getValue(){
    return value;
}
  • 1
  • 2
  • 3

递增计数器是如何实现的:

public final int incrementAndGet(){
    for(;;){
        int current = getValue();
        int next = value + 1;
        if(compareAndSet(current,next)){
            return next;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

该方法采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。 
而compareAndSet操作利用了Java本地接口(JNI,Java Native Interface)完成CPU指令的操作:

public final boolean compareAndSet(int expect, int update){
    return unsafe.compareAndSwapInt(this,valueOffset,expect,unsafe);
}
  • 1
  • 2
  • 3
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
      try {
        valueOffset = unsafe.objectFieldOffset(*.class.getDeclaredField("value"));
      } catch (Exception ex) { 
      throw new Error(ex); }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

【2】“ABA”问题

1、可以发现,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。 
2、什么是”ABA”问题? 
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。 
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

【3】用AtomicStampedReference/AtomicMarkableReference解决ABA问题

例子:用AtomicStampedReference解决ABA问题:

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

}

猜你喜欢

转载自blog.csdn.net/qq_35956041/article/details/80875340