AtomicStampedReference解决CAS机制中ABA问题

AtomicStampedReference解决CAS机制中ABA问题

​ 硬件中存在并发操作的原语,从而在硬件层面提升效率。在intel的CPU中,使用cmpxchg指令。在Java发展初期,java语言是不能够利用硬件提供的这些便利来提升系统的性能的。而随着java不断的发展,Java本地方法(JNI)的出现,使得java程序越过JVM直接调用本地方法提供了一种便捷的方式。CAS理论是它实现整个java包的基石。

​ CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)

CAS虽然很高效地解决了原子操作,但是CAS仍然存在三个问题
1)ABA问题。因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2)循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

3)只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。

这个时候就可以用锁。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。

AtomicStampedReference解决CAS机制中ABA问题Demo

只有版本号相同才可以进行cas修改

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

public class Test {
    /** 
    **/

    public static void main(String[] args) {
        AtomicInteger integer = new AtomicInteger(0);
        AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(100,1000);
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                integer.compareAndSet(0,1);
                integer.compareAndSet(1,0);
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean b = integer.compareAndSet(0, 1);
                System.out.println("AtomicInteger替换");
                if(b) System.out.println("0已经被替换为1");
                else System.out.println("替换失败");
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reference.compareAndSet(100,-100,
                        reference.getStamp(), reference.getStamp()+1);
                reference.compareAndSet(-100,100,
                        reference.getStamp(), reference.getStamp()+1);
            }
        });
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = reference.getStamp();
                try {
                    Thread.currentThread().sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean b = reference.compareAndSet(100, -100,
                        stamp, reference.getStamp() + 1);
                System.out.println("AtomicStampedReference替换");
                if(b) System.out.println("100已经被替换为-100");
                else System.out.println("替换失败");
            }
        });
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
输出结果:
AtomicInteger替换
0已经被替换为1
AtomicStampedReference替换
替换失败
上一篇:spring学习(三)DI 依赖注入


下一篇:LeetCode:647. 回文子串