Java Basics: ABA Problems with Atomic References

1. ABA problems

Q: Talk about the ABA problem of the atomic class AtomicInteger? Do you know what atomic update references are? How to solve the ABA problem
Answer:
1. CAS will cause "ABA problem".

An important prerequisite for the implementation of the CAS algorithm is to take out the data at a certain time in the memory, and then compare and exchange. In the case of multi-threading, there is a case where the data is modified by other threads after the data is fetched.

1) For example, the data fetched by thread 1 from the main memory is A, and then perform some business operations (for example, it takes 5s), and finally prepare to change the data to C; 2) At this time, thread 2 seizes the CPU resources, and then performs
some Business operations (for example, it takes 2s), and then change the shared data to B;
3) At this time, thread 1 is still performing business operations, so thread 3 seizes the cpu resource, and then performs some business operations (for example, it takes 2s), and then changes The shared data is changed to A;
at this time, the business operation of thread 1 is over. When it comes to cas, it finds that the data is A. After comparing and exchanging, it is changed to C.

The above steps can be understood as the ABA problem of CAS.

To explain, if the final result of the data is only required to be A, and you don’t care about whether there are other changes in the middle, then this problem has little or no impact.

But, for example, the data is the user's money, or the volume of goods in the warehouse. Every operation should be supervised. It is impossible to say that someone embezzled money or misappropriated goods to sell. When someone came to check, the data was added back. Although the final data is correct, there must be problems with this.

We need technology to solve the above problems.

2. In addition to the ABA problem, in the case of multi-threading, the AtomicInteger class was used to solve the atomicity problem of n++. So what if you want to solve the atomicity problem of a certain custom class ? At this point you can use atomic update references: AtomicReference

3. To solve the ABA problem, you can use an atomic reference with a timestamp: AtomicStampedReference

To understand the ABA problem mentioned in this article well, you must first fully understand the concept and function of CAS. If you are not sure, you can read this blog post first (Java Basics: Detailed Explanation of CAS ) .

1. Verify ABA issues through atomic reference codes

In the previous two blog posts, when explaining atomicity, n++ was used as an example.

However, in actual development, there must be many custom classes, and atomic references are also required at this time. Then you can use the atomic reference under the concurrent package: AtomicReference.

First create a warehouse goods class: WareHouseGoods.

package com.koping.test;

public class WareHouseGoods {
    
    
    public String name;

    public String type;

    public int number;

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getType() {
    
    
        return type;
    }

    public void setType(String type) {
    
    
        this.type = type;
    }

    public int getNumber() {
    
    
        return number;
    }

    public void setNumber(int number) {
    
    
        this.number = number;
    }

    public WareHouseGoods(String name, String type, int number) {
    
    
        this.name = name;
        this.type = type;
        this.number = number;
    }

    @Override
    public String toString() {
    
    
        return "WareHouseGoods{" +
                "name='" + name + '\'' +
                ", type='" + type + '\'' +
                ", number=" + number +
                '}';
    }
}

Then use the code to verify the ABA problem. The verification code is as follows, and the running result is as shown in the figure below.

It can be seen that when thread 1 processes the logic (within 5s), thread 2 sells itself, and then takes some goods back to the warehouse.
At this time, when thread 1 finds that there are still 100 pieces in the warehouse, it takes them out and sells them directly.

I don’t even know that there is an ABA problem in the middle. Someone who took your goods and sold them at a high price and bought them at a low price may have already made money, or they may put them in the warehouse for you. These thread 1 didn't even know it, and it succeeded.

package com.koping.test;

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

public class AbaDemo {
    
    
    static WareHouseGoods wareHouseGoods1 = new WareHouseGoods("裙子", "女性", 100);
    static WareHouseGoods wareHouseGoods2 = new WareHouseGoods("裙子", "女性", 80);
    static WareHouseGoods wareHouseGoods3 = new WareHouseGoods("裙子", "女性", 50);

    static AtomicReference<WareHouseGoods> atomicReference = new AtomicReference<>(wareHouseGoods1);

    public static void main(String[] args) {
    
    
        // 线程1需要取50件货物进行销售,剩余50件。但是在之前,线程1会先做一些业务操作,假设5s.
        new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            System.out.println("线程1是否成功拿出50件: " + atomicReference.compareAndSet(wareHouseGoods1, wareHouseGoods3));
            System.out.println("现在仓库货物的总数为:" + atomicReference.get());
        },"线程1").start();

        new Thread(() -> {
    
    
            // 当前程1做业务逻辑操作时,线程2可以先取出20件进行销售,剩余80件;
            // 然后再自己补回20件到仓库中,因此剩余还是100件。
            atomicReference.compareAndSet(wareHouseGoods1, wareHouseGoods2);
            System.out.println("线程2已拿出20件进行销售,现在仓库货物的总数为:" + atomicReference.get());
            atomicReference.compareAndSet(wareHouseGoods2, wareHouseGoods1);
            System.out.println("线程2已放回20件到仓库中,现在仓库货物的总数为:" + atomicReference.get());
        },"线程2").start();
    }
}

insert image description here

2. Solve the ABA problem through atomic references with timestamps

To solve the ABA problem in the previous section, you can use a timestamped atomic reference: AtomicStampedReference.

1) Each batch of goods has a version number, and the goods have an initial version number: 1.
2) When the version number obtained by thread 1 from the database is 1, it still does business operations;
3) At this time, thread 2 takes out and puts back the goods privately, although the final goods are still 100 pieces, the singular version number has been changed to 3;
the running result is as shown in the figure below, it can be seen that thread 1 failed when it got 50 items at this time, because it found that the version number was not the previous version number 1. At this time, the system knows that someone has moved the goods in the warehouse, so it can be traced.

package com.koping.test;

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

public class AbaDemo {
    
    
    static WareHouseGoods wareHouseGoods1 = new WareHouseGoods("裙子", "女性", 100);
    static WareHouseGoods wareHouseGoods2 = new WareHouseGoods("裙子", "女性", 80);
    static WareHouseGoods wareHouseGoods3 = new WareHouseGoods("裙子", "女性", 50);

    // 初始版本号是1
    static AtomicStampedReference<WareHouseGoods> atomicReference = new AtomicStampedReference<>(wareHouseGoods1, 1);

    public static void main(String[] args) {
    
    
        // 线程1需要取50件货物进行销售,剩余50件。但是在之前,线程1会先做一些业务操作,假设5s.
        new Thread(() -> {
    
    
            // 先从数据库中获取之前的版本号,再进行业务操作
            int stamp = atomicReference.getStamp();
            try {
    
    
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            System.out.println("\n线程1是否成功拿出50件: " + atomicReference.compareAndSet(wareHouseGoods1, wareHouseGoods3, stamp, stamp+1));
            System.out.println("现在仓库货物的总数为:" + atomicReference.getReference());
        },"线程1").start();

        new Thread(() -> {
    
    
            // 当前程1做业务逻辑操作时,线程2可以先取出20件进行销售,剩余80件;
            // 然后再自己补回20件到仓库中,因此剩余还是100件。
            atomicReference.compareAndSet(wareHouseGoods1, wareHouseGoods2, atomicReference.getStamp(), atomicReference.getStamp()+1);
            System.out.println("线程2已拿出20件进行销售,现在仓库货物的总数为:" + atomicReference.getReference());
            System.out.println("此时仓库货物的版本号为: " + atomicReference.getStamp());
            atomicReference.compareAndSet(wareHouseGoods2, wareHouseGoods1, atomicReference.getStamp(), atomicReference.getStamp()+1);
            System.out.println("线程2已放回20件到仓库中,现在仓库货物的总数为:" + atomicReference.getReference());
            System.out.println("此时仓库货物的版本号为: " + atomicReference.getStamp());
        },"线程2").start();
    }
}

insert image description here

Guess you like

Origin blog.csdn.net/xueping_wu/article/details/124601769