Java中CAS-ABA的问题解决方案

忻州SEO摘要

CAS即对比交换,它在保证数据原子性的前提下尽可能的减少了锁的使用,很多编程语言或者系统实现上都大量的使用了CAS。

了解CAS(Compare-And-Swap)

CAS即对比交换,它在保证数据原子性的前提下尽可能的减少了锁的使用,很多编程语言或者系统实现上都大量的使用了CAS。

JAVA中CAS的实现

JAVA中的cas主要使用的是Unsafe方法,Unsafe的CAS操作主要是基于硬件平台的汇编指令,目前的处理器基本都支持CAS,只不过不同的厂家的实现不一样罢了。

Unsafe提供了三个方法用于CAS操作,分别是


1

<p style="line-height: 1.5em;">public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);<br>public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);<br>public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);<br></p>

  • value 表示 需要操作的对象
  • valueOffset 表示 对象(value)的地址的偏移量(通过Unsafe.objectFieldOffset(Field valueField)获取)
  • expect 表示更新时value的期待值
  • update 表示将要更新的值

具体过程为每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false

Unsafe类中compareAndSwapInt的具体实现:


1

<p style="line-height: 1.5em;">UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))<br>  UnsafeWrapper("Unsafe_CompareAndSwapInt");<br>  oop p = JNIHandles::resolve(obj);<br>  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;<br>UNSAFE_END<br></p>

ABA问题

线程1准备用CAS修改变量值A,在此之前,其它线程将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了。

aba.png

例子


1

<p style="line-height: 1.5em;">public static AtomicInteger a = new AtomicInteger(1);public static void main(String[] args){<br>    Thread main = new Thread(() -> {<br>        System.out.println("操作线程" + Thread.currentThread() +",初始值 = " + a);  //定义变量 a = 1<br>        try {<br>            Thread.sleep(1000);  //等待1秒 ,以便让干扰线程执行<br>        } catch (InterruptedException e) {<br>            e.printStackTrace();<br>        }        boolean isCASSuccess = a.compareAndSet(1,2); // CAS操作<br>        System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess);<br>    },"主操作线程");<br><br>    Thread other = new Thread(() -> {<br>        Thread.yield();  //确保thread-main线程优先执行<br>        a.incrementAndGet(); // a 加 1, a + 1 = 1 + 1 = 2<br>        System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ a);<br>        a.decrementAndGet(); // a 减 1, a - 1 = 2 - 1 = 1<br>        System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ a);<br>    },"干扰线程");<br><br>    main.start();<br>    other.start();<br>}<br></p>

输出> 操作线程Thread[主操作线程,5,main],初始值 = 1>
操作线程Thread[干扰线程,5,main],【increment】 ,值 = 2>
操作线程Thread[干扰线程,5,main],【decrement】 ,值 = 1>
操作线程Thread[主操作线程,5,main],CAS操作结果: true

解决ABA方案

思路

解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。

aba_2.png

JAVA中ABA中解决方案(AtomicStampedReference)

AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

//关键代码

public class AtomicStampedReference<V> {

    private static class Pair<T> {

        final T reference;  //维护对象引用

        final int stamp;  //用于标志版本

        private Pair(T reference, int stamp) {

            this.reference = reference;

            this.stamp = stamp;

        }

        static <T> Pair<T> of(T reference, int stamp) {

            return new Pair<T>(reference, stamp);

        }

    }

    private volatile Pair<V> pair;

    ....

    /**

      * expectedReference :更新之前的原始值

      * newReference : 将要更新的新值

      * expectedStamp : 期待更新的标志版本

      * newStamp : 将要更新的标志版本

      */

    public boolean compareAndSet(V   expectedReference,

                                 V   newReference,

                                 int expectedStamp,

                                 int newStamp) {

        Pair<V> current = pair; //获取当前pair

        return

            expectedReference == current.reference && //原始值等于当前pair的值引用,说明值未变化

            expectedStamp == current.stamp && // 原始标记版本等于当前pair的标记版本,说明标记未变化

            ((newReference == current.reference &&

              newStamp == current.stamp) || // 将要更新的值和标记都没有变化

             casPair(current, Pair.of(newReference, newStamp))); // cas 更新pair

    }

}

例子


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

private static AtomicStampedReference<Integer> atomicStampedRef =

        new AtomicStampedReference<>(10);

public static void main(String[] args){

    Thread main = new Thread(() -> {

        System.out.println("操作线程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference());

        int stamp = atomicStampedRef.getStamp(); //获取当前标识别

        try {

            Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行

        catch (InterruptedException e) {

            e.printStackTrace();

        }

        boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1);  //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败

        System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess);

    },"主操作线程");

    Thread other = new Thread(() -> {

        Thread.yield(); // 确保thread-main 优先执行

atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);

        System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference());

        atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);

        System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());

    },"干扰线程");

    main.start();

    other.start();

}

// 输出

> 操作线程Thread[主操作线程,5,main],初始值 a = 2

> 操作线程Thread[干扰线程,5,main],【increment】 ,值 = 2

> 操作线程Thread[干扰线程,5,main],【decrement】 ,值 = 1

> 操作线程Thread[主操作线程,5,main],CAS操作结果: false

来源:内蒙古SEO

原文地址:https://www.cnblogs.com/1994jinnan/p/11985687.html

时间: 2024-10-29 06:15:19

Java中CAS-ABA的问题解决方案的相关文章

CAS -- ABA问题的解决方案

我们现在来说什么是ABA问题.假设内存中有一个值为A的变量,存储在地址V中. 此时有三个线程想使用CAS的方式更新这个变量的值,每个线程的执行时间有略微偏差.线程1和线程2已经获取当前值,线程3还未获取当前值. 接下来,线程1先一步执行成功,把当前值成功从A更新为B:同时线程2因为某种原因被阻塞住,没有做更新操作:线程3在线程1更新之后,获取了当前值B. 在之后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成了A. 最后,线程2终于恢复了运行状态,由于阻塞之前已经获得了“当前值A

Java中CAS底层实现原理分析

CAS(无锁优化.自旋锁)原理分析 一.CAS(compareAndSwap)的概念 CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制. CAS(V, A, B),V为内存地址.A为预期原值,B为新值.如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值.否则,说明已经被其他线程更新,处理器不做任何操作:无论哪种情况,它都会在 CAS 指令之前返回该位置的值.而我们可以使用自旋锁,循环CAS,重新读取该变量再尝试再次修改该变量,也

详解java中CAS机制所导致的问题以及解决——内存顺序冲突

[CAS机制] 指的是CompareAndSwap或CompareAndSet,是一个原子操作,实现此机制的原子类记录着当前值的在内存中存储的偏移地址,将内存中的真实值V与旧的预期值A做比较,如果不一致则说明内存中的值被其他线程修改过了,返回false,否则将新值B存入内存. Java内部是使用本地调用类unsafe实现的. Java原子类底层原理就是采用CAS机制. 可能会出现什么问题 aba问题: 线程1取出A之后被阻塞了,此时线程2把内存中A改为B,一系列操作后又改为A,此时线程1恢复执行

java中你所不知道的CAS操作

1.CAS是什么 Compare and Swap(比较并操作),由处理器架构支持,语义是如果当前值V和旧值A相同,则将当前值修改为B,如果不相同则不修改.CAS操作采用的是乐观锁技术,当多线程同时修改某个变量时只有一个成功,其他线程会失败当是不会被挂起,会被告知失败并重试.2.CAS操作和synchronized有什么区别呢 synchronized关键字采用悲观锁技术,线程独享锁,其他线程会被挂起知道锁被释放线程恢复,挂起和恢复会有很大的开销.3.java中CAS操作有哪些 java1.5之

volatile | CAS| ABA

JMM JMM(Java内存模型Java Memory Model)是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段.静态字段和构成数组对象的元素)的访问方式. JMM关于同步的规定: ①线程解锁前,必须把共享变量的值刷新回主内存: ②线程解锁前,必须读取主内存的最新值到自己的工作内存: ③加锁解锁是同一把锁: ①把变量读到各个线程的工作内存中:②线程运算完之后把变量的值改好,然后把它写回主内存:③可见性--让其他线程马上知道这个值: 各个线

Java并发编程原理与实战四十三:CAS ---- ABA问题

CAS(Compare And Swap)导致的ABA问题 问题描述 多线程情况下,每个线程使用CAS操作欲将数据A修改成B,当然我们只希望只有一个线程能够正确的修改数据,并且只修改一次.当并发的时候,其中一个线程已经将A成功的改成了B,但是在线程并发调度过程中尚未被调度,在这个期间,另外一个线程(不在并发中的请求线程)将B又修改成了A,那么原来并发中的线程又可以通过CAS操作将A改成B 测试用例: public class AbaPro { private static final Rando

Java CAS ABA问题发生的场景分析

提到了CAS操作存在问题,就是在CAS之前A变成B又变回A,CAS还是能够设置成功的,什么场景下会出现这个问题呢?查了一些资料,发现在下面的两种情况下会出现ABA问题. 1.A最开始的内存地址是X,然后失效了,有分配了B,恰好内存地址是X,这时候通过CAS操作,却设置成功了 这种情况在带有GC的语言中,这种情况是不可能发生的,为什么呢?拿JAVA举例,在执行CAS操作时,A,B对象肯定生命周期内,GC不可能将其释放,那么A指向的内存是不会被释放的,B也就不可能分配到与A相同的内存地址,CAS失败

GDAL在java中乱码问题解决方案

[前序] 首先关于GDAL源码方面,GDAL开源项目源码是使用C++语言所写,通过源码的编译可以生成支持一系列语言如c++/java/php/csharp/perl/python/ruby开发所依赖的第三方包或头文件.GDAL的同时支持不同平台下的编译生成,在Win平台下使用起来出现的问题较多,比如编译时选择不同的编译选项32位或x64位产生出不同的依赖库,还有中文路径.中文读写问题等等. 在使用GDAL在java环境下进行读写数据文件时,遇到java语言读tab数据源出现乱码问题,具体的情况描

Java在J2EE工程中路径寻址问题终极解决方案

前言 Java的路径问题,非常难搞.最近的工作涉及到创建和读取文件的工作,这里我就给大家彻底得解决Java路径问题. Java路径 Java中使用的路径,分为两种:绝对路径和相对路径.具体而言,又分为四种: 一. URI形式的绝对资源路径 如:file:/D:/java/eclipse32/workspace/jbpmtest3/bin/aaa.b URL是URI的特例.URL的前缀/协议,必须是Java认识的.URL可以打开资源,而URI则不行. URL和URI对象可以互相转换,使用各自的to