GeekTop
  • 首页
  • 关于

GeekTop

不止代码

解决 ABA 问题

解决 ABA 问题

2022年7月14日 Alex Comments 0 Comment

CAS (Compare and set)很好用,但是也有问题:1、CAS 本质上是自旋锁,在锁竞争比较激烈的情况下或者单核 CPU 的情况下,性能并不高。这个问题不算严重,最多就是使用不当性能差点;2、存在 ABA 问题。这个问题比较严重,可能导致数据不一致的问题。

CAS 需要在操作的时候检查数据有没有变化,如果没有发生变化就更新,但是如果一个数据本来是 A,第一个线程看到的也是 A,此时又另外一个线程把数据改成 B,又改成 A,此时第一个线程看到的还是 A,但其实数据已经发生了变化。

ABA 是会产生问题的。设想一种情况,用来本来有 50 积分,用户提交了充值 100 积分的订单,如果一个线程要给用户充 100 积分,此时另外一个线程抢先一步给这个用户充值了 100 积分,然后用户把这 100 积分消费掉,此时第一个线程看到的仍然是 50 积分,以为数据没有变化,于是再给 用户充值 100 积分。虽然这种情况发生的概率很小,但是在高并发的情况下是有可能发生的。

产生 ABA 问题的根源在于第一个看到数据的线程没有感知到数据的变化,解决这个问题的做法是添加版本号,基于数据库的乐观锁就是这个做的,在 java 标准库里也有类似的实现:AtomicStampedReference。

AtomicStampedReference内部加了一个时间戳作为版本号。

基本方法如下:

//构造方法, 传入引用和戳
public AtomicStampedReference(V initialRef, int initialStamp)
//返回引用
public V getReference()
//返回版本戳
public int getStamp()
//如果当前引用 等于 预期值并且 当前版本戳等于预期版本戳, 将更新新的引用和新的版本戳到内存
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp)
//如果当前引用 等于 预期引用, 将更新新的版本戳到内存
public boolean attemptStamp(V expectedReference, int newStamp)
//设置当前引用的新引用和版本戳
public void set(V newReference, int newStamp) 

看下面的例子:

public class ABATest {

    public static void main(String[] args) {
        testStamp();
    }

    private static void testStamp() {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

        new Thread(()->{
            int[] stampHolder = new int[1];
            int value = atomicStampedReference.get(stampHolder);
            int stamp = stampHolder[0];
            System.out.println("thread 1 read value: " + value + ", stamp: " + stamp);

            // 阻塞1s
            LockSupport.parkNanos(1000000000L);

            if (atomicStampedReference.compareAndSet(value, 3, stamp, stamp + 1)) {
                System.out.println("thread 1 update from " + value + " to 3");
            } else {
                System.out.println("thread 1 update fail!");
            }
        }).start();

        new Thread(()->{
            int[] stampHolder = new int[1];
            int value = atomicStampedReference.get(stampHolder);
            int stamp = stampHolder[0];
            System.out.println("thread 2 read value: " + value + ", stamp: " + stamp);
            if (atomicStampedReference.compareAndSet(value, 2, stamp, stamp + 1)) {
                System.out.println("thread 2 update from " + value + " to 2");

                // do sth

                value = atomicStampedReference.get(stampHolder);
                stamp = stampHolder[0];
                System.out.println("thread 2 read value: " + value + ", stamp: " + stamp);
                if (atomicStampedReference.compareAndSet(value, 1, stamp, stamp + 1)) {
                    System.out.println("thread 2 update from " + value + " to 1");
                }
            }
        }).start();
    }
}

Class AtomicStampedReference

Guide to AtomicStampedReference in Java


AI

Post navigation

NEXT
使用 Apple 的 Keychain 保存 SSH 的 passphase
PREVIOUS
关于 macOS 上面部分 emoji 无法显示的问题

发表回复 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

最近文章

  • 姥姥
  • 设计一个可扩展的用户模型
  • 使用 Apple 的 Keychain 保存 SSH 的 passphase
  • 解决 ABA 问题
  • 关于 macOS 上面部分 emoji 无法显示的问题
  • 这些年我技术栈的变化
  • 搬瓦工、狗云、hostodo、oracle对比测试
  • Google Drive 无法上传文件
  • 使用 socks5 代理 git ssh 协议
  • 到底要不要“润”
  • 可复用的代码
  • 关于疫情的一点点反思
  • 我的风控策略(投资篇)
  • 我的风控策略(生活篇)
  • Spring Boot是如何处理异常的
  • 编码与解码
  • 基于统计的图像目标检索
  • 有限状态机和状态模式
  • API安全设计
  • 用户密码的存储策略

近期评论

  • ǝɔɐǝԀʎzɐɹϽ发表在《可复用的代码》
  • Alex发表在《可复用的代码》
  • ǝɔɐǝԀʎzɐɹϽ发表在《到底要不要“润”》
  • ǝɔɐǝԀʎzɐɹϽ发表在《可复用的代码》
  • ǝɔɐǝԀʎzɐɹϽ发表在《我的风控策略(生活篇)》
  • ǝɔɐǝԀʎzɐɹϽ发表在《如何写出简洁优雅的代码》
  • 张志亮发表在《如何写出简洁优雅的代码》

分类

  • AI (2)
  • Java应用安全之道 (1)
    • 加密与解密 (1)
  • Odoo (2)
  • Python (1)
  • 图像处理 (1)
  • 年鉴 (1)
  • 数据库 (10)
  • 编程 (14)
    • Spring (1)
  • 读书笔记 (2)
  • 运维 (5)
  • 随笔 (10)

归档

  • 2023年1月 (2)
  • 2022年8月 (1)
  • 2022年7月 (4)
  • 2022年6月 (2)
  • 2022年5月 (2)
  • 2022年4月 (3)
  • 2021年10月 (1)
  • 2021年7月 (1)
  • 2021年5月 (1)
  • 2020年11月 (1)
  • 2020年7月 (1)
  • 2020年3月 (2)
  • 2020年2月 (1)
  • 2019年1月 (1)
  • 2018年12月 (2)
  • 2018年11月 (2)
  • 2017年4月 (1)
  • 2016年11月 (1)
  • 2016年9月 (1)
  • 2016年7月 (1)
  • 2016年5月 (3)
  • 2016年4月 (2)
  • 2016年3月 (1)
  • 2016年2月 (2)
  • 2015年12月 (1)
  • 2015年11月 (2)
  • 2015年8月 (1)
  • 2015年4月 (1)
  • 2015年3月 (1)

标签

Database devops Java MySQL PostgreSQL Python shell Spring SpringBoot Spring Security 安全 年鉴 总结 编程 随笔
© 2015-2023   Geektop.net All Rights Reserved.