栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java中解决CAS机制出现的ABA问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java中解决CAS机制出现的ABA问题

Java中解决CAS机制出现的ABA问题? 1、先了解一下什么是CAS?

一句话总结就是: 比较并交换(compare and swap)是一条CPU并发原语
CAS的公式如下:

CAS(V,A,B)
1:V 表示内存中的地址
2:A 表示预期值
3:B 表示要修改的新值

CAS的功能:就是预期值A与内存中的值相比较,如果相同则将内存中的值改变成新值B

2、CAS的底层原理?

换句话说也就是CAS为什么能保证原子性?
(1)靠的是底层的Unsafe类
(2)Unsafe类是CAS的核心类,由于java无法直接访问底层系统,需要通过本地(native)方法访问,Unsafe相当于一个后门,该类可以直接操作特定的内存数据。 Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为java中的CAS依赖于Unsafe类中的方法
(3)注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底昃资源执行相应任务,Unsafe类中的native方法是调用底层原语,原语是有原子性的

3、CAS的问题?

(1)循环时间长,开销比较大
CAS是Java乐观锁的一种实现机制,在Java并发包中,大部分类就是通过CAS机制实现的线程安全,它不会阻塞线程,如果更改失败则可以自旋重试,允许多线程并发修改,但是互相比较,互相比较以后直到全部的线程执行成功,并发性加强了,但是循环时间长,开销大。

解决办法:JVM支持处理器提供的pause指令,使得效率会有一定的提升,pause指令有两个作用:
(1)第一它可以延迟流水线执行指令,使CPU不会消耗过多的执行资源
(2)第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)
     而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

(2)只能保证一个共享变量的原子操作
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性

解决办法:从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,
        你可以把多个变量放在一个对象里来进行CAS操作。

(3)ABA问题
形象的说就是,狸猫换太子
ABA问题就是 讲桌上面放了1瓶水,张三有10秒的操作时间,他先把水换成了 水果,用了2秒,接着他又把水果换成了水,尽管最后的结果没有发生改变,但是这之间有很多次的操作机会,所以就造成了漏洞,也就是常说的狸猫换太子(比喻:你老婆出轨之后又回来,还是原来的老婆吗?)

4、怎么解决ABA问题?

AtomicReference原子引用
如果赋值操作不是线程安全的。若想不用锁来实现,可以用**AtomicReference**这个类,实现对象引用的原子更新

两种解决方法:

(1)AtomicStampedReference 类:
    版本号原子引用,理解原子引用+新增一种机制,那就是修改版本号(类似时间戳)
(2)AtomicMarkableReference 类

(1)AtomicStampedReference 示例:
看示例代码之前,请先去看看AtomicStampedReference 方法的API可以帮助理解
链接: 点击这里查看方法各个参数的含义.
示例:

package com.song.test01;

import java.util.concurrent.atomic.AtomicStampedReference;


public class Test09 {
    public static void main(String[] args) {
        String str1 = "aaa";
        String str2 = "bbb";
        //1、传入初始引用 和 初始标志 :aaa 和 1
        AtomicStampedReference reference = new AtomicStampedReference(str1,1);
        System.out.println("reference.getReference() = " + reference.getReference()+",版本号为"+reference.getStamp());

        System.out.println("============================================");

        
        boolean b1 = reference.compareAndSet(str1, str2, reference.getStamp(), reference.getStamp() + 1);
        System.out.println("b1:"+b1);
        System.out.println("reference.getReference() = " + reference.getReference()+",版本号为"+reference.getStamp());

        System.out.println("===========================================");

        
        boolean b2 = reference.attemptStamp(str2, reference.getStamp() + 1);
        System.out.println("b2: "+b2);
        //这里把版本号 改为 3
        System.out.println("reference.getStamp() = "+reference.getStamp());

        System.out.println("==========================================");

        
        boolean c = reference.weakCompareAndSet(str2,"ccc",4, reference.getStamp()+1);
        System.out.println("reference.getReference() = " + reference.getReference()+",版本号为"+reference.getStamp());
        System.out.println("c = " + c);

    }
}


输出结果:

通过stamp这个标记(版本号)属性来记录CAS每次设置值的操作,而下一次再CAS操作时,由于期望的stamp与现有的stamp不一样,因此就会设值失败,从而杜绝了ABA问题的复现

(2)AtomicMarkableReference
基本和AtomicStampedReference差不多,AtomicStampedReference主要关注版本号,即reference的值被修改了多少次;AtomicMarkableReference是使用boolean mark来标记reference是否被修改过

既然有了 AtomicStampedReference 为啥还需要再提供 AtomicMarkableReference 呢,在现实业务场景中,不关心引用变量被修改了几次,只是单纯的关心是否更改过。

查看示例前可以看看 API,方便理解各个参数
链接: 点击这里查看方法的各个参数含义.

示例:

package com.song.test01;

import java.util.concurrent.atomic.AtomicMarkableReference;

public class Test10 {
    
    static AtomicMarkableReference atomicStampedReference = new AtomicMarkableReference("cat",false);

    public static void main(String[] args) {
        // public boolean isMarked() 返回标记的当前值。
        boolean oldMarked = atomicStampedReference.isMarked();
        //public V getReference() 返回参考的当前值。
        String oldReference = atomicStampedReference.getReference();

        System.out.println("初始化之后的标记:"+oldMarked);
        System.out.println("初始化之后的值:"+oldReference);

        System.out.println("==============================================");

        String newReference = "dog";

        boolean b =atomicStampedReference.compareAndSet(oldReference,newReference,true,false);
        if(!b){
            System.out.println("Mark不一致,无法修改Reference的值");
        }
        b =atomicStampedReference.compareAndSet(oldReference,newReference,false,true);
        if(b){
            System.out.println("Mark一致,修改reference的值为dog");
        }
        System.out.println("修改成功之后的Mark:"+atomicStampedReference.isMarked());
        System.out.println("修改成功之后的值:"+atomicStampedReference.getReference());
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/397276.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号