Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >AtomicStampedReference是怎样解决CAS的ABA问题

AtomicStampedReference是怎样解决CAS的ABA问题

作者头像
龟仙老人
发布于 2020-12-15 06:46:33
发布于 2020-12-15 06:46:33
1.8K01
代码可运行
举报
文章被收录于专栏:捉虫大师捉虫大师
运行总次数:1
代码可运行

什么是ABA问题

但凡对Java有一点深入就会知道 CAS,即 compareAndSwap。在Java中使用 Unsafe 类提供的native方法可以直接操作内存,其中就有对compareAndSwap的实现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

这里的 var1 指的是 当前对象,var2 是当前对象中 var1 的某个属性的偏移量,var4 是偏移量 var2 对应的属性的期望值,var5 是要交换的值,当对象的属性是期望值时,交互成功,否则交换失败,这是一个原子操作,只有一个线程会成功。所以它也是很多Java的线程安全实现的前提。

举个例子AtomicInteger count的初始值为 5,有两个线程期望将其设置为 10;此时两个线程都调用compareAndSet(底层也是compareAndSwapObject

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    final AtomicInteger count = new AtomicInteger(5);

    for (int i = 0; i < 2; i++) {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (Exception ignore) {
            }
            boolean re = count.compareAndSet(5, 10);
            System.out.println(Thread.currentThread().getName() + " compareAndSet " + re);
        });
        thread.start();
    }
}

会如我们所愿,只有一个线程会成功。

这时如果再有一个线程将10设为5

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    final AtomicInteger count = new AtomicInteger(5);

    for (int i = 0; i < 2; i++) {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (Exception ignore) {
            }
            boolean re = count.compareAndSet(5, 10);
            System.out.println(Thread.currentThread().getName() + " compareAndSet " + re);
        });
        thread.start();
    }

    Thread thread = new Thread(() -> {
        try {
            Thread.sleep(10);
        } catch (Exception ignore) {
        }
        boolean re = count.compareAndSet(10, 5);
        System.out.println(Thread.currentThread().getName() + " compareAndSet " + re);
    });
    thread.start();
}

你可能会看到这样的结果:

正常吗?正常,也不正常。说正常是按照我们的理解它就可能是这样的结果,但多执行几次你会发现还可能有这样的结果:

我们期望的5设置为10只有一个线程会成功结果成功了两个,好比给用户充值,余额5块,充值到10块,手快点了两次,同时也用这个账户来支付了一笔5块钱的订单,期望的是充值只有一笔成功,支付可以成功,按照后一种结果相当于充值了2次5块钱。这就是CAS中的ABA问题。

AtomicStampedReference 如何解决ABA问题

如何解决?思路很简单,每次compareAndSwap后给数据的版本号加1,下次compareAndSwap的时候不仅比较数据,也比较版本号,值相同,版本号不同也不能执行成功。Java中提供了AtomicStampedReference来解决该问题

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    final AtomicStampedReference<Integer> count = new AtomicStampedReference<>(5, 1);

    for (int i = 0; i < 2; i++) {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (Exception ignore) {
            }

            boolean re = count.compareAndSet(5, 10, 1, 2);
            System.out.println(Thread.currentThread().getName() + "[recharge] compareAndSet " + re);
        });
        thread.start();
    }

    Thread thread = new Thread(() -> {
        try {
            Thread.sleep(10);
        } catch (Exception ignore) {
        }
        boolean re = count.compareAndSet(10, 5, count.getStamp(), count.getStamp() + 1);
        System.out.println(Thread.currentThread().getName() + "[consume] compareAndSet " + re);
    });
    thread.start();
}

这就能保证充值只有一次能成功了。那么它是如何每次都让版本号更新的?

AtomicStampedReference 内部维护了一个 Pair数据结构,用volatile修饰,保证可见性,用于打包数据对象和版本号

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static class Pair<T> {
    final T reference;
    final int stamp;
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
}

它的compareAndSet方法如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}
  • 首先判断传入的参数是否符合 Pair 的预期,从数据和版本号两个方面来判断,有一个不符合就打回;
  • 如果传入的参数与Pair中的一样,直接返回true,不用更新;
  • 使用casPair来比较交换当前的Pair与传入参数构成的Pair
  • casPair又调用compareAndSwapObject来交互Pair属性。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

所以简单来说,AtomicStampedReference是通过加版本号来解决CASABA问题。至于怎么加版本号,因为compareAndSwapObject只能对比交互一个对象,所以只需要将数据和版本号打包到一个对象里就解决问题了。

同样Java中提供了AtomicMarkableReference,与 AtomicStampedReference 原理类似,只不过 AtomicMarkableReference 将版本号换成了一个 bool 值,只关心数据是否“被修改过”,而 AtomicStampedReference 可以关心数据被修改了多少次。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 捉虫大师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
编辑精选文章
换一批
18.AtomicReference、AtomicStampReference底层原理。多个变量更新怎么保证原子性?CAS的ABA问题怎么解决?
老王:小陈啊,上一章我们说了AtomicInteger、AtomicBoolean的底层原理,这一篇我们就来说说Atomic系列的另一个分类AtomicReference和AtomicStampReference。
终有救赎
2023/10/16
2140
18.AtomicReference、AtomicStampReference底层原理。多个变量更新怎么保证原子性?CAS的ABA问题怎么解决?
AtomicStampedReference解决CAS的ABA问题
AtomicStampReference 解决CAS的ABA问题 什么是ABA ABA问题:指CAS操作的时候,线程将某个变量值由A修改为B,但是又改回了A,其他线程发现A并未改变,于是CAS将进行值交换操作,实际上该值已经被改变过,这与CAS的核心思想是不符合的 ABA解决方案 每次变量更新的时候,把变量的版本号进行更新,如果某变量被某个线程修改过,那么版本号一定会递增更新,从而解决ABA问题 AtomicReference 演示ABA问题 package com.keytech.task; impo
开源日记
2021/01/11
4090
AtomicStampedReference 源码分析
AtomicStampedReference 是对 AtomicReference 的一个补充,解决了在 CAS 场景下 ABA 的问题
itliusir
2020/02/10
4400
JAVA并发编程系列(3)JUC包之CAS原理
首先,Atomic包,原子操作类,提供了用法简单、性能高效、最重要是线程安全的更新一个变量。支持整型、长整型、布尔、double、数组、以及对象的属性原子修改,支持种类非常丰富。
拉丁解牛说技术
2024/09/06
1370
Java并发编程CAS
它的功能是判断内存某一个位置的值是否为预期,如果是则更改这个值,这个过程就是原子的。
一觉睡到小时候
2020/05/27
4720
ABA问题
我们开发一个网站,需要对访问量进行统计,用户每发送一次请求,访问量+1,如何实现?
用户4283147
2022/10/27
2850
ABA问题
【Java】CAS及其缺点和解决方案梳理
CAS 英文就是 compare and swap ,也就是比较并交换,首先它是一个原子操作,可以避免被其他线程打断。在Java并发中,最初接触的应该就是Synchronized关键字了,但是Synchronized属于重量级锁,很多时候会引起性能问题,虽然在新的 JDK 中对其已经进行了优化。volatile也是个不错的选择,但是volatile不能保证原子性,只能在某些场合下使用。那么问题来了,这个 CAS 机制是怎么在不加锁的情况下来保证共享资源的互斥呢?
后端码匠
2023/09/02
4410
【Java】CAS及其缺点和解决方案梳理
jdk源码分析之AtomicStampedReference--原子变量ABA问题解决方案
上一篇讲到,jdk1.5引入了并发包,java.util.concurrent.atomic包下有很多原子变量的操作类,但是基本都存在一个问题,ABA问题,也就是并发场景下原子变量被修改了,然后又被改回来,线程再做CAS计算的时候无法感知当前的原子变量的内容已经不是当初的内容了,只是单纯的比较值是否相等无法满足一些特定的业务场景。当然Doug Lea大神也考虑到了这个问题,于是就引入了AtomicStampedReference和AtomicMarkableReference,接下来就AtomicStampedReference的原理和用法做一下详细的介绍。
叔牙
2020/11/19
4510
jdk源码分析之AtomicStampedReference--原子变量ABA问题解决方案
Java并发必知必会第三弹:用积木讲解ABA原理 |老婆居然又听懂了!
上一节我们讲了程序员深夜惨遭老婆鄙视,原因竟是CAS原理太简单?,留了一个彩蛋给大家,ABA问题是怎么出现的,为什么不是AAB拖拉机,AAA金花,4个A炸弹 ?这一篇我们再来揭开ABA的神秘面纱。
悟空聊架构
2020/08/27
3690
【死磕Java并发】—- 深入分析CAS
CAS,Compare And Swap,即比较并交换。Doug lea大神在同步组件中大量使用CAS技术鬼斧神工地实现了Java多线程的并发操作。整个AQS同步组件、Atomic原子类操作等等都是以
芋道源码
2018/03/01
7770
【死磕Java并发】—- 深入分析CAS
死磕 java并发包之AtomicStampedReference源码分析
AtomicStampedReference是java并发包下提供的一个原子类,它能解决其它原子类无法解决的ABA问题。
彤哥
2019/07/08
7130
死磕 java并发包之AtomicStampedReference源码分析
慕课网高并发实战(四)- 线程安全性
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要额外的同步或协同,这个类都能表现出正确行为,那么就称这个类是线程安全的
Meet相识
2018/09/12
5390
慕课网高并发实战(四)- 线程安全性
AtomicStampedReference解决CAS的ABA问题
我们都知道在使用CAS方法进行无锁自加或者更换栈的表头之类的问题时会出现ABA问题 Java中使用AtomicStampedReference来解决CAS中的ABA问题,它不再像compareAndSet方法中只比较内存中的值也当前值是否相等,而且先比较引用是否相等,然后比较值是否相等,这样就避免了ABA问题。 那么AtomicStampedReference的基本用法是什么呢?看如下:
名字是乱打的
2022/05/13
2540
Java--CAS操作分析
CAS操作在Java中的应用很广泛,比如ConcurrentHashMap,ReentrantLock等,其常被用来解决独占锁对线程阻塞而导致的性能低下问题,是高效并发必备的一种优化方法.
屈定
2018/09/27
1.8K0
Java--CAS操作分析
【多线程系列】高效的 CAS (Compare and Swap)
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
Lorin 洛林
2023/11/05
3470
【多线程系列】高效的 CAS (Compare and Swap)
(70) 原子变量和CAS / 计算机程序的思维逻辑
从本节开始,我们探讨Java并发工具包java.util.concurrent中的内容,本节先介绍最基本的原子变量及其背后的原理和思维。 原子变量 什么是原子变量?为什么需要它们呢? 在理解synchronized一节,我们介绍过一个Counter类,使用synchronized关键字保证原子更新操作,代码如下: public class Counter { private int count; public synchronized void incr(){ coun
swiftma
2018/01/31
7730
并发设计模式 之 CAS算法
对于并发控制而言,我们平时用的锁(synchronized,Lock)是一种悲观的策略。它总是假设每一次临界区操作会产生冲突,因此,必须对每次操作都小心翼翼。如果多个线程同时访问临界区资源,就宁可牺牲性能让线程进行等待,所以锁会阻塞线程执行。
JAVA日知录
2019/12/10
7540
并发设计模式 之 CAS算法
面试|详解CAS及其引发的三个问题
在多线程编程的时候,为了保证多个线程对一个对象可以安全进行访问时,我们需要加同步锁synchronized,保证对象的在使用时的正确性,synchronized就是一种独占锁,它会导致所有需要此锁的线程挂起,等待锁的释放。
Spark学习技巧
2019/07/08
7.4K0
还在用Synchronized?Atomic你了解不?
之前在学习的时候也看过AtomicInteger类很多次了,一直没有去做相关的笔记。现在遇到问题了,于是就过来写写笔记,并希望在学习的过程中解决掉问题。
Java3y
2018/12/18
6230
还在用Synchronized?Atomic你了解不?
Java并发基础:了解无锁CAS就从源码分析
CAS的全称为Compare And Swap,直译就是比较交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,在intel的CPU中,使用的是cmpxchg指令,就是说CAS是靠硬件实现的,从而在硬件层面提升效率。
程序员鹏磊
2018/08/04
7050
相关推荐
18.AtomicReference、AtomicStampReference底层原理。多个变量更新怎么保证原子性?CAS的ABA问题怎么解决?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验