【JUC】7.原子类

1. 什么是原子类

原子类是指java.util.concurrent.atomic下的类

在这里插入图片描述

其中这十六个类可以分为以下

  • 基本类型原子类
  • 数据类型原子类
  • 引用类型原子类
  • 对象的属性修改原子类
  • 原子操作增强类

1.1 基本类型原子类

基本类型原子类包括

  • AtomicInteger
  • AtomicBoolean
  • AtomicLong

相关API

  • public final int get()//获取当前的值
  • public final int getAndSet(int newValue)//获取当前的值,并设置新的值
  • public final int getAndIncrement()//获取当前的值,并自增
  • public final int getAndDecrement)//获取当前的值,并自减
  • public final int getAndAdd(int delta)//获取当前的值,并加上预期的值
  • boolean compareAndSet(int expect, int update)//如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
class MyNumber{
    AtomicInteger atomicInteger = new AtomicInteger();

    public void addPlusPlus(){
        atomicInteger.getAndIncrement();
    }
}


public class AtomicIntegerDemo {

    public static final int SIZE = 50;

    @SneakyThrows
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);

        for (int i = 0; i < SIZE; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000; j++) {
                        myNumber.addPlusPlus();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + "result:" + myNumber.atomicInteger.get());
    }
}

在这里插入图片描述

这里CountDownLatch是用来计算线程是否完成的


1.2 数组类型原子类

数组类型原子类包括以下

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

这种类型比较简单,上当与将基本类型原子类进一步变成数组

public class AtomicIntegerArrayDemo {
    public static void main(String[] args) {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1, 2, 3, 4, 5});
        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            System.out.println(atomicIntegerArray.get(i));
        }
    }
}

在这里插入图片描述


1.3 引用类型原子类

引用类型原子类包括以下

  • AtomicReference
  • AtomicStampedReference
    • 携带版本号的引用类型原子类,可以解决ABA问题
    • 解决修改过几次
    • 状态戳原子引用
  • AtomicMarkableReference
    • 原子更新带有标记位的引用类型对象
    • 解决是否修改过
      • 它的定义是将状态戳简化为true|false
    • 状态戳原子引用

1.4 对象的属性修改原子类

  • AtomicIntegerFieldUpdater:原子更新对象中的int类型字段的值
  • AtomicLongFieldUpdater:原子更新对象中Long类型字段的值
  • AtomicReferenceFieldUpdater:原子更新引用类型字段的值
class BankAccount {
    String bandName = "CCB";

    public volatile int money = 0;

    AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");

    public void transMoney(BankAccount bankAccount) {
        fieldUpdater.getAndIncrement(bankAccount);
    }
}


public class AtomicReferenceFieldUpdaterDemo {
    public static void main(String[] args) throws InterruptedException {
        BankAccount bankAccount = new BankAccount();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000; j++) {
                        bankAccount.transMoney(bankAccount);
                    }
                }finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + "result:" + bankAccount.money);
    }
}
class MyVar{
    public volatile Boolean isInit = Boolean.FALSE;
    AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater =
            AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
    @SneakyThrows
    public void init(MyVar myVar){
        if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)){
            System.out.println(Thread.currentThread().getName() + "\t" + "------ start init, need 2 s");
            TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName() + "\t" + "====== over init");
        }else {
            System.out.println(Thread.currentThread().getName() + "\t" + "====== 已有线程初始化");
        }
    }
}

public class AtomicReferenceFieldUpdaterDemo {
    public static void main(String[] args) {
        MyVar myVar = new MyVar();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            }, String.valueOf(i)).start();
        }
    }
}

在这里插入图片描述


1.5 原子操作增强类

原子操作增强类是指:

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

在这里插入图片描述

原子操作类是JDK1.8出现的

在阿里巴巴Java开发手册中提到:如果是count++操作,使用如下类实现AtomicInteger count = new AtomicInteger();

count.addAndGet(1);如果是JDK8,推荐使用LongAdder对象,这比AtomicLong性能更好(减少乐观锁)

这类通常是最好的AtomicLong当多个线程共同更新和用于用途,如收集统计,不为细粒度的同步控制。在低更新争用下,两个类具有相似的特征。但在高争用,预计这一类的吞吐量显着更高,在更高的空间消耗的费用。

就以LongAdder而言,常用API如下

方法名说明
void add(long x)将当前的value加x
void increment()将当前的value加1
void decrement()将当前的value减1
long sum()返回当前值,特别注意,在没有并发更新value的情况下,sum会返回一个精确值,存在并发情况下,sum不保证返回精确值
void reset()将value重置为0,可用于替代重新new一个LongAdder,但此方法只可以在没有并发更新的情况下
long sumThenReset()获取当前value,并将value重置为0
public class LongAdderAPIDemo {
    public static void main(String[] args) {
        LongAdder longAdder = new LongAdder();

        //1+1
        longAdder.increment();
        //1+1+1
        longAdder.increment();
        //1+1+1+1
        longAdder.increment();
        //1+1+1+1+1
        longAdder.increment();
        System.out.println(longAdder.sum());

        //5代表初始值,第一个参数代表模式
        LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 5);
        //1 + 5
        longAccumulator.accumulate(1);
        //1 + 5 + 4
        longAccumulator.accumulate(4);
        System.out.println(longAccumulator.get());
    }
}

模拟高并发点赞

class ClickNumber {
    /**
     * 使用重量级锁synchronized
     */
    int number = 0;

    public synchronized void clickBySynchronized() {
        number++;
    }

    /**
     * 使用atomicLong
     */
    AtomicLong atomicLong = new AtomicLong(0);

    public void clickByAtomicLong() {
        atomicLong.getAndIncrement();
    }

    /**
     * longAdder默认从0开始
     */
    LongAdder longAdder = new LongAdder();

    public void clickByLongAdder() {
        // + 1
        longAdder.increment();
    }

    /**
     * 规定为x + y
     * 从0开始
     */
    LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);

    public void clickByLongAccumulator() {
        longAccumulator.accumulate(1);
    }

}

/**
 * 50个线程,每个线程100w次,总点赞数出来
 */
public class AccumulatorCompareDemo {
    public static final int _1W = 10000;
    public static final int threadNumber = 50;

    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();

        CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);

        long starTime, endTime;
        starTime = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 100 * _1W; j++) {
                        clickNumber.clickBySynchronized();
                    }
                } finally {
                    countDownLatch1.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("-----costTime:" + (endTime - starTime) + "毫秒\tclickBySynchronized: " + clickNumber.number);

        starTime = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 100 * _1W; j++) {
                        clickNumber.clickByAtomicLong();
                    }
                } finally {
                    countDownLatch2.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("-----costTime:" + (endTime - starTime) + "毫秒\tclickByAtomicLong: " + clickNumber.atomicLong.get());


        starTime = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 100 * _1W; j++) {
                        clickNumber.clickByLongAdder();
                    }
                } finally {
                    countDownLatch3.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("-----costTime:" + (endTime - starTime) + "毫秒\tclickByLongAdder: " + clickNumber.longAdder.sum());

        starTime = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 100 * _1W; j++) {
                        clickNumber.clickByLongAccumulator();
                    }
                } finally {
                    countDownLatch4.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("-----costTime:" + (endTime - starTime) + "毫秒\tclickBySynchronized: " + clickNumber.longAccumulator.get());

    }
}

在这里插入图片描述

通过这个案例可以发现,LongAdder的性能更快


2. LongAdder底层原理

为什么LongAdder更快呢?

前面说到AtomicLong是通过CAS实现的,当线程数少的的话,通过自旋锁等待解决

LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。

sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前Atomicl.ong一个value的更新压力分散到多个value中去,从而降级更新热点。

LongAdder的base变量:低并发,直接累加到该变量上

Cell[]数组:高并发,累加进各个线程自己的槽


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起名方面没有灵感

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值