JAVA语言模拟CAS的ABA问题

什么是CAS机制

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较和替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

 

如何保证多核心下的线程安全?

系统底层进行CAS操作的时候,会判断当前系统是否是多核心系统,如果是就给"总线"加锁,只有一个线程能对总线加锁成功,加锁成功之后会进行CAS操作,CAS的原子性是平台级别的.

CAS的缺点

ABA问题

CAS需要在操作值的时候检查下值有没有变化,如果没有变化则更新,但是如果一个值原来是A,在CAS方法执行之前,被其它线程修改为了B,然后又修改回了A,那么CAS方法检查的时候就会发现它的值没有发生变化,但是实际上它发生了变化,这就是CAS的ABA问题.

下面用一个实例代码模拟ABA问题:

package com.springcloud.server.springserver.thread;

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

public class ABADemo {
    public static AtomicInteger a = new AtomicInteger(1);

    public static void main(String[] args) {
        Thread main = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("操作线程"+Thread.currentThread().getName()+", 初始值"+a.get());
                try {
                    int expectNum = a.get();
                    int newNum = expectNum + 1;
                    TimeUnit.SECONDS.sleep(1);//休眠一秒钟,让出cpu
                    boolean b = a.compareAndSet(expectNum, newNum);
                    System.out.println("操作线程"+Thread.currentThread().getName()+",CAS操作: "+b);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "主线程");

        Thread other = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.MICROSECONDS.sleep(20);

                    a.incrementAndGet();
                    System.out.println("操作线程" + Thread.currentThread().getName() + "increment,值: " + a.get());
                    a.decrementAndGet();
                    System.out.println("操作线程" + Thread.currentThread().getName() + "decrement,值: " + a.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "other");
        main.start();
        other.start();
    }
}

输出结果:

在第一个线程获取值然后进行CAS操作的过程中,other线程将值该为2,然后又改回1,此时main线程进行CAS判断的时候并没有发现a的值有变化,这就是典型的ABA问题.

解决ABA问题

使用版本号机制,给值加一个修改的版本号,每次值变化,都会修改它的版本号,CAS操作时都会去对比此版本号.

JAVA8中ABA的解决办法:使用AtomicStampedReference,先上代码,再解释:

package com.springcloud.server.springserver.thread;

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

public class ABAStampDemo {
    public static AtomicStampedReference<Integer> a = new AtomicStampedReference<Integer>(1,1);

    public static void main(String[] args) {
        Thread main = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("操作线程"+Thread.currentThread().getName()+", 初始值"+a.getReference());
                try {
                    Integer expectNum = a.getReference();
                    Integer newNum = expectNum + 1;
                    Integer stampOld = a.getStamp();
                    Integer stampNew = stampOld+1;
                    TimeUnit.SECONDS.sleep(1);//休眠一秒钟,让出cpu
                    boolean b = a.compareAndSet(expectNum, newNum,stampOld,stampNew);
                    System.out.println("操作线程"+Thread.currentThread().getName()+",CAS操作: "+b +",stamp:"+ a.getStamp());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "主线程");

        Thread other = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.MICROSECONDS.sleep(20);

                    a.compareAndSet(a.getReference(),a.getReference()+1,a.getStamp(),a.getStamp()+1);
                    System.out.println("操作线程" + Thread.currentThread().getName() + "increment,值: " + a.getReference()+",stamp:"+ a.getStamp());
                    a.compareAndSet(a.getReference(),a.getReference()-1,a.getStamp(),a.getStamp()+1);
                    System.out.println("操作线程" + Thread.currentThread().getName() + "decrement,值: " + a.getReference()+",stamp:"+ a.getStamp());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "other");
        main.start();
        other.start();
    }
}

输出结果:

AtomicStampedReference的构造方法里面,第一个是初始值,第二个是初始化的版本号,使用compareAndSet方法的时候,需要传入四个参数:

expectedReference:期望值
newReference:新的值
expectedStamp:期望的版本号
newStamp:新的版本号

只有当期望值和从内存里面读取的值一样,而且期望的版本号和当前内存里面的版本号一样的时候,新的值和版本号才会更改,方法返回true,否则返回false,在并发程度高而且不能忽视ABA问题的场景下,可以使用这个类进行问题的解决.

更多关于版本号的理解,参考另外一篇博文:

https://blog.csdn.net/thetimelyrain/article/details/100974565

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值