原子相关的引用数据类型

原子相关的引用数据类型

有些时候我们需要保证引用数据类型的原子操作,在java.util.concurrent.atomic包下提供了下面的原子类可以保证引用数据类型的原子操作。

  • AtomicReference
  • AtomicStampedReference
  • AtomicMarkableReference

使用AtomicReference模拟抢红包

创建红包类

import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;

/**
 *  红包
 */
public class Money implements Runnable {

    //红包总额,要保证原子性
    private AtomicReference<BigDecimal> total;

    public Money(BigDecimal money) {
        this.total = new AtomicReference<>(money);
    }

    @Override
    public void run() {
        while (true){
            BigDecimal now = total.get();
            //如果总额小于0,则说明已被抢完
            if (now.compareTo(BigDecimal.ZERO) <= 0) {
                System.out.println("已抢完");
                break;
            }

            //每次被抢的金额,固定为2元
            BigDecimal spend = new BigDecimal("2");
            //计算被抢之后的余额
            BigDecimal after = now.subtract(spend);

            //自旋
            if (total.compareAndSet(now, after)) {
                System.out.println("剩余:" + total.get());
                break;
            }
        }
    }

}

创建测试类:

import java.math.BigDecimal;


public class Test {

    public static void main(String[] args) {
        //红包总额
        Money money = new Money(new BigDecimal("10"));

        //开启15个线程来抢红包
        for (int i = 0; i < 15; i++) {
            new Thread(money).start();
        }
    }
}

使用AtomicStampedReference解决ABA的问题

AtomicStampedReference通过添加版本号的方式来获知修改的次数。

import java.util.concurrent.atomic.AtomicStampedReference;


public class Test {

    //设定名称和版本号
    private static AtomicStampedReference<String> atomicName = new AtomicStampedReference("tony", 0);

    public static void main(String[] args) {

        //先获取版本号便于后面的测试
        int version = atomicName.getStamp();

        //t1线程修改name,版本号加1
        new Thread(() -> {
            atomicName.compareAndSet(atomicName.getReference(), "bill", atomicName.getStamp(), atomicName.getStamp() + 1);
            System.out.println("t1将name修改为bill,当前版本是:" + atomicName.getStamp());
        }).start();

        try {
            Thread.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //t2修改name,版本号加1
        new Thread(() -> {
            atomicName.compareAndSet(atomicName.getReference(), "tony", atomicName.getStamp(), atomicName.getStamp() + 1);
            System.out.println("t2将name修改为tony,当前版本是:" + atomicName.getStamp());
        }).start();

        try {
            Thread.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //main线程在修改时,这里的版本号已经发生了变化,所以会失败
        boolean flag = atomicName.compareAndSet(atomicName.getReference(), "bill", version, ++version);
        System.out.println(flag);
        System.out.println(atomicName.getReference());

    }
}

AtomicMarkableReference的使用

有些情况下我们不关心修改了几次,只是关心是否有修改过,此时可以使用AtomicMarkableReference来解决。compareAndSet方法中的参数:

  • expectedReference

    旧的值

  • newReference

    新修改之后的值

  • expectedMark

    期望的标记值

  • newMark

    修改之后的标记值

import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicStampedReference;


public class Test {

    //设定名称和版本号
    private static AtomicMarkableReference<String> atomicName = new AtomicMarkableReference("tony", true);

    public static void main(String[] args) {


        //t1线程修改name,将标记为设置为false,表示修改过了
        new Thread(() -> {
            atomicName.compareAndSet(atomicName.getReference(), "bill", true, false);
        }).start();

        try {
            Thread.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //main线程发现已经被修改过了,所以这里会修改失败
        boolean flag = atomicName.compareAndSet(atomicName.getReference(), "tony", true, false);
        System.out.println(flag);

    }
}