【JUC】原子操作

发布时间 2023-05-09 13:43:50作者: 日月星宿

 

juc包下的原子类

针对基础类型地原子性读写而设计的原子类:

    AtomicBoolean
    AtomicInteger
    AtomicIntegerArray
    AtomicIntegerFieldUpdater<T>
    AtomicLong
    AtomicLongArray
    AtomicLongFieldUpdater<T>

针对引用类型地原子性读写而设计的原子类:

    AtomicReference<V>    
    AtomicReferenceArray<E>
    AtomicReferenceFieldUpdater<T,V>
    AtomicMarkableReference<V>
    AtomicStampedReference<V>

 

AtomicReference

  该类提供了对象引用的非阻塞原子性读写操作。

源码:

public class AtomicReference<V> implements java.io.Serializable {
    private static final long serialVersionUID = -1848883965231344442L;
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    //volatile修饰了一个泛型的value属性
    private volatile V value;
}

非线程安全的代码测试:

public static void main(String[] args) {
        Prize prize = new Prize("小米汽车", 100);
        AtomicInteger atomicInteger = new AtomicInteger();
        IntStream.range(0, 300).forEach(
                value -> {
                    new Thread(
                            () -> {
                                //①获得当前还剩多少号
                                int count = prize.getCount();
                                if (count > 0) {
                                    //②对剩余号源减1,并更新回奖池
                                    prize.setCount(count - 1);
                                    atomicInteger.incrementAndGet();
                                    log.info("当前线程:{},抢到了 {} 号", Thread.currentThread().getName(), count);
                                }
                            }
                    ).start();
                }
        );
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("中奖人数:{}", atomicInteger.get());
    }
以上代码存在线程不安全。其不安全的本质是:当线程1、线程2同时执行到处,假如都获得了当前剩余号数10,继续往下执行到处,都对其进行了减1,最终两个线程更新回去却是9;针对这种情况我们有很多解决方案,这里我选择使用AtomicReference类进行测试:
 
首先定义需要引用的奖品类:
//定义我们的奖品类
@Data
public class Prize {

    /**
     * 一等奖:小米汽车
     */
    private String level;

    /**
     * 数量
     */
    private int count;

    public Prize(String level, int count) {
        this.level = level;
        this.count = count;
    }
}

接下来对该类创建的对象做引用类型的原子性操作:

public static void main(String[] args) {
        //将我们的初始奖池封装到AtomicReference中
        AtomicReference<Prize> reference = new AtomicReference<>(new Prize("小米汽车", 100));
        AtomicInteger atomicInteger = new AtomicInteger(0);
        IntStream.range(0, 300).forEach(
                value -> {
                    new Thread(
                            () -> {
                                //①获得当前还剩多少号的对象
                                final Prize prize = reference.get();
                                if (prize.getCount() > 0) {
                                      //②对剩余号源进行减1
                                    Prize prizeNew = new Prize(prize.getLevel(), reference.get().getCount() - 1);
                                      //③将数据更新到奖池
                                    if (reference.compareAndSet(prize, prizeNew)) {
                                        log.info("当前线程:{},抢到了 {} 号", Thread.currentThread().getName(), prize.getCount());
                                        atomicInteger.incrementAndGet();
                                    }
                                }
                            }
                    ).start();
                }
        );
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("中奖人数:{}", atomicInteger.get());
    }
观察上面代码:虽然①②处也会出现之前基础版本的情况,但是最终将数据刷新回奖池的时候,如果prize对象的引用已经被其他线程修改,则当前线程执行**reference.compareAndSet(prize, prizeNew)**会更新失败。对于这个线程来说,好气呀,手都伸进抽奖箱了,还是没有抢到大奖;对于老板来说无伤大雅,只管送出指定数量即可;针对这种可以搞一个while循环让线程进行重试(摸一次就可以了嘛,还想摸多少次?)

 

 

 

 

 

 

 

原文链接:并发编程之AtomicReference - 掘金 (juejin.cn)