JUC原子类


多线程并发操作时,诸如"i++" 等操作是不具备原子性的,不是线程安全的操作,通常情况下大家会使用 synchronized 关键字来将这些不安全的操作变成同步操作 , 但是性能很低呀。JDK 提供了一些原子类,相比于 synchronized ,线程运行效率更高。

JUC中的Atomic原子操作包

在这里插入图片描述

1、基本原子类

  • AtomicInteger 整型原子类

  • AtomicBoolean 布尔型原子类

  • AtomicLong 长整型原子类

功能:通过原子方式更新Java基础类型变量的值

常用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)

原子类如何保证线程安全的呢?举个栗子玩玩:

public class AtomicDemo {
    static class MyNumber{
        AtomicInteger atomicInteger = new AtomicInteger();
        public void addPlusPlus(){
            atomicInteger.getAndIncrement();
        }
    }
    public static class AtomicIntegerDemo1 {
        public static final int SIZE = 50;
        public static void main(String[] args) {
            MyNumber myNumber = new MyNumber();
            for(int i = 1;i <= SIZE;i ++){
                new Thread(() -> {
                    for(int j = 1;j <= 1000;j ++){
                        myNumber.addPlusPlus();
                    }
                },String.valueOf(i)).start();
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"result: "+myNumber.atomicInteger);
        }
    }
    /**
     * 运行结果:
     * main    result: 39000   第一次运行
     * main    result: 41000   第二次运行
     *
     * 进程已结束,退出代码0
     * 预期结果应该是50000呀,为什么会出现这种情况呢?
     * 因为上面的  50* 1000个计算还没结束,他就去get数值了,
     * 来看看解决方案:
     */
    //方法一:线程暂停一会儿(不推荐生产环境,自己玩还行)
    public static class AtomicIntegerDemo2 {
        public static final int SIZE = 50;
        public static void main(String[] args) {
            MyNumber myNumber = new MyNumber();
            for(int i = 1;i <= SIZE;i ++){
                new Thread(() -> {
                    for(int j = 1;j <= 1000;j ++){
                        myNumber.addPlusPlus();
                    }
                },String.valueOf(i)).start();
            }
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"result: "+myNumber.atomicInteger);
        }
    }

    /**
     * 运行结果:
     * main    result: 50000
     *
     * 进程已结束,退出代码0
     *
     * 直接暂停线程一点都不优雅,
     * 我们想要的效果是当计算完之后马上去获取值,这样效率才会高呀
     * 使用  CountDownLatch
     */
    //方法二-减法计数器 CountDownLatch
    public static class AtomicIntegerDemo3 {
        public static final int SIZE = 50;
        public static void main(String[] args) throws InterruptedException {
            MyNumber myNumber = new MyNumber();
            CountDownLatch countDownLatch = new CountDownLatch(SIZE);
            for(int i = 1;i <= SIZE;i ++){
                new Thread(() -> {
                    try {
                        for(int j = 1;j <= 1000;j ++){
                            myNumber.addPlusPlus();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                },String.valueOf(i)).start();
            }
            countDownLatch.await();
            System.out.println(Thread.currentThread().getName()+"\t"+"result: "+myNumber.atomicInteger);
        }
    }
    /**
     * 运行结果:
     * main    result: 50000
     *
     * 进程已结束,退出代码0
     */
}

基本原子类常用api 玩法:

public class AtomicIntegerDemo {
    //初始值1
    private static final AtomicInteger atomicInteger = new AtomicInteger(1);
    //默认值false
    private static final AtomicBoolean atomicBoolean = new AtomicBoolean(Boolean.FALSE);
    //默认值1
    private static final AtomicLong atomicLong = new AtomicLong(1L);

    public static void main(String[] args) {
        System.out.println("初始值:" + atomicInteger.get());
        System.out.println("获取当前值并设置新值:" + atomicInteger.getAndSet(2) +"\t" + "设置后的值:"+atomicInteger.get());
        System.out.println("获取当前值并自增:" + atomicInteger.getAndIncrement() +"\t" + "自增后的值:"+atomicInteger.get());
        System.out.println("获取当前值并自减:" + atomicInteger.getAndDecrement() +"\t" + "自减后的值:"+atomicInteger.get());
        System.out.println("获取当前值并加上期望值:" + atomicInteger.getAndAdd(10)+"\t" + "加上期望值后的值:"+atomicInteger.get());
        System.out.println("如果输入的数值等于预期值,则以原子方式将该值设置为输入值:" + atomicInteger.compareAndSet(12, 2021)+"\t"+atomicInteger.get());

        System.out.println("--------------");
        System.out.println("初始值:" + atomicBoolean.get());
        atomicBoolean.set(Boolean.TRUE);
        System.out.println(atomicBoolean.getAndSet(Boolean.FALSE)+"\t"  + "获取并设置后:" + atomicBoolean.get());
        System.out.println(atomicBoolean.compareAndSet(Boolean.FALSE, Boolean.TRUE)+"\t" + "比较并设置后:" + atomicBoolean.get());
        //AtomicLong    一样的操作 
    }
        /**
         * 运行结果:
         * 初始值:1
         * 获取当前值并设置新值:1    设置后的值:2
         * 获取当前值并自增:2  自增后的值:3
         * 获取当前值并自减:3  自减后的值:2
         * 获取当前值并加上期望值:2   加上期望值后的值:12
         * 如果输入的数值等于预期值,则以原子方式将该值设置为输入值:true   2021
         * --------------
         * 初始值:false
         * true    获取并设置后:false
         * true    比较并设置后:true
         *
         * 进程已结束,退出代码0
         */
}

看下源码,api方法返回的是 原来的值 , 需要调get()才能获取设置之后的值

2、数组原子类

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicRreferenceArray

常用api :
public final int get(int i): 获取index=i位置元素的值

public final int getAndSet(int i , int newValue):返回index = i 位置当前值,并将其设置为新值

public final int getAndIncrement( int i ):获取index = i 位置元素的值,并自增

public final int getAndDecrement( int i ):获取index = i 位置元素的值,并自减

public final int getAndAdd(int delta):获取index = i 位置元素的值,并加上预期的值

boolean compareAndSet(int expect, int update):如果输入的数值等于预期值,则以原子方式将w位置 i 的元素值设置为输入值(update)

public final void lazySet (int i , int new value) :最终将位置i的元素设置为newValue,该方法可能导致其他线程在之后的一小段时间内还是可以读取到旧值

Case

public class AtomicArrayDemo {
    public static class AtomicIntegerArrayDemo
    {
        public static void main(String[] args)
        {
            AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);//0 0 0 0 0
            //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
            //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});//1 2 3 4 5

            for (int i = 0; i <atomicIntegerArray.length(); i++) {
                System.out.println(atomicIntegerArray.get(i));
            }
            System.out.println();
            int tmpInt = 0;

            tmpInt = atomicIntegerArray.getAndSet(0,1122);
            System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));

            tmpInt = atomicIntegerArray.getAndIncrement(0);
            System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));

        }
        /**
         * 运行结果:
         * 0
         * 0
         * 0
         * 0
         * 0
         *
         * 0   1122
         * 1122    1123
         *
         * 进程已结束,退出代码0
         */
    }
}

3、引用类型原子类

基础的原子类型只能保证一个变量的原子操作,当需要对多个变量进行操作时,CAS无法保证原子操作,这时可以使用原子引用类型保证对象引用的原子性

简单来说:如果需要同时保证对多个变量操作的原子性,就可以把多个变量放在一个对象中进行操作

  • AtomicReference

    • 可以带泛型
  • AtomicStampedReference

    • 携带版本号的引用类型原子类,可以解决ABA问题。解决修改过几次的问题
  • AtomicMarkableReference

    • 解决是否修改过,它的定义就是将状态戳简化为true/false,类似一次性筷子

Demo

聊聊AtomicStampedReference 解决ABA问题

public class AtomicMarkableReferenceDemo {

    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+atomicStampedReference.getReference()+"\t 首次版本号:"+stamp);//1-----------初始获得一样的版本号
            //暂停500毫秒,保证t4线程初始化拿到的版本号和我一样,
            try { TimeUnit.MILLISECONDS.sleep( 500 ); } catch (InterruptedException e) { e.printStackTrace(); }

            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+atomicStampedReference.getReference()+"\t 2次版本号:"+atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+atomicStampedReference.getReference()+"\t 3次版本号:"+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();//记录一开始的版本号,并且写死
            System.out.println(Thread.currentThread().getName()+"\t"+atomicStampedReference.getReference()+"\t 首次版本号:"+stamp);//1------------初始获得一样的版本号
            //暂停1秒钟线程,等待上面的t3线程,发生了ABA问题
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            boolean result = atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);//这个还是初始的版本号,但是实际上版本号被T3修改了,所以肯定会失败
            System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }
    /**
     * 运行结果:
     * t3  100     首次版本号:1
     * t4  100     首次版本号:1
     * t3  101     2次版本号:2
     * t3  100     3次版本号:3
     * t4  false  100
     *
     * 进程已结束,退出代码0
     */

AtomicMarkableReference解决ABA问题

public class AtomicMarkableReferenceDemo {

    static AtomicMarkableReference markableReference = new AtomicMarkableReference(100,false);

    public static void main(String[] args) {
        new Thread(()->{
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t"+"默认标识"+marked);
            //暂停1秒钟线程,等待后面的t2线程和我拿到一样的flag标识,都是false
            try { TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
            markableReference.compareAndSet(100, 1000, marked, !marked);
        },"t1").start();

        new Thread(()->{
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t"+"默认标识"+marked);
            //这里停2秒,让t1先修改,然后t2试着修改
            try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
            
            boolean t2Result = markableReference.compareAndSet(100, 2000, marked, !marked);
            System.out.println(Thread.currentThread().getName()+"\t"+"t2线程result--"+t2Result);
            
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.isMarked());
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference());

        },"t2").start();
    }
    /**
     * 运行结果:
     * t1  默认标识false
     * t2  默认标识false
     * t2  t2线程result--false
     * t2  true
     * t2  1000   //解释:t2修改失败 值是t1修改的
     *
     * 进程已结束,退出代码0
     */
}

AtomicStampedReference:携带版本号的引用类型原子类,可以解决ABA问题,状态戳原子引用解决修改过几次

AtomicMarkableReference:原子更新带有标记位的引用类型对象,它的定义就是将状态戳简化为true|false,类似一次性筷子,不关心引用变量更改过几次,只关心是否更改过

ABA问题解决方案

  • 很多乐观锁的实现版本都是使用版本号(Version)来解决ABA问题,乐观锁在每次执行数据的修改操作时都会带上一个版本号,该版本号和数据的版本号一致就可以执行修改操作并对版本号加1 , 否则执行失败。
  • JDK 提供了一个AtomicStampedReference类来解决ABA问题,AtomicStampedReference 在CAS的基础上增加了一个Stamp ,使用这个印戳可以察觉数据是否发生变化。
  • AtomicStampedReference 的compareAndSet()方法首先检查当前对象引用值是否等于预期引用,并且当前印戳(Stamp)是否等于预期标志,如果全部相等,就以原子的方式将引用值和印戳标志的值更新为指定的更新值。

4、对象的属性修改原子类

  • AtomicIntegerFieldUpdater : 原子更新对象中int类型字段的值
  • AtomicLongFieldUpdater : 原子更新对象中Long类型字段的值
  • AtomicReferenceFieldUpdater : 原子更新引用类型字段的值

以一种线程安全的方式操作非线程安全对象内的某些字段

使用要求:

  1. 更新的对象属性必须使用 public volatile 修饰符。
  2. 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性

Demo

/**
 * 需求:
 * 1000个人同时向一个账号转账一元钱,那么累计应该增加1000元,
 * 除了synchronized和 CAS ,还可以使用AtomicIntegerFieldUpdater来实现。
 */
public class AtomicIntegerFieldUpdaterDemo {
    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount();

        for (int i = 1; i <= 1000; i++) {
            new Thread(() -> {
                bankAccount.transferMoney(bankAccount);
            },"t1").start();
        }
        //暂停毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(bankAccount.money);//运行结果:1000

    }
}

class BankAccount {
    //更新钱数
    static final AtomicIntegerFieldUpdater<BankAccount> accountAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
    public volatile int money = 0;//钱数
    private String bankName = "CCB";//银行

    //不加锁+性能高,局部微创
    public void transferMoney(BankAccount bankAccount) {
        accountAtomicIntegerFieldUpdater.incrementAndGet(bankAccount);
    }
}
/**
 * AtomicReferenceFieldUpdaterDemo
 * 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
 */
public class AtomicReferenceFieldUpdaterDemo {
    public static void main(String[] args) {
        MyVar myVar = new MyVar();
        //这里有五个线程,看那个线程可以修改成功
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            }, String.valueOf(i)).start();
        }
    }

}

class MyVar {
    //默认未初始化
    public volatile Boolean isInit = Boolean.FALSE;
    static final AtomicReferenceFieldUpdater<MyVar, Boolean> atomicReferenceFieldUpdater =
            AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");

    public void init(MyVar myVar) {
        if (atomicReferenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
            System.out.println(Thread.currentThread().getName() + "\t" + "---init.....");
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "---init.....over");
        } else {
            System.out.println(Thread.currentThread().getName() + "\t" + "------其它线程正在初始化");
        }
    }
    /**
     * 第一次运行结果:
     * 2   ---init.....
     * 3   ------其它线程正在初始化
     * 1   ------其它线程正在初始化
     * 4   ------其它线程正在初始化
     * 5   ------其它线程正在初始化
     * 2   ---init.....over
     *
     * 第二次运行结果:
     * 1   ---init.....
     * 5   ------其它线程正在初始化
     * 4   ------其它线程正在初始化
     * 2   ------其它线程正在初始化
     * 3   ------其它线程正在初始化
     * 1   ---init.....over
     *
     * 进程已结束,退出代码0
     */

}

5、原子操作增强类原理深度解析

在争用激烈的场景下,会导致大量的CAS空自旋,这会导致大量的CPU浪费,如何提高CAS操作性能呢?

java8提供的类

  • DoubleAccumulator

  • DoubleAdder

  • LongAccumulator

  • LongAdder

    • 以空间换时间的方式提升高并发场景下CAS操作的性能
    • 核心思想:热点分离

Demo

public class LongAdderAPI {
    public static void main(String[] args) {

        /**
         *  DoubleAdder只能用来计算加法,且从零开始计算
         */
        DoubleAdder doubleAdder = new DoubleAdder();
        //加指定值
        doubleAdder.add(2d);
        doubleAdder.add(-2d);
        //返回当前值,特别注意:在没有并发的情况下返回的准确值,在高并发情况下返回的不能保证准确性(瞬间值)
        System.out.println(doubleAdder.sum());//0.0
        //重置doubleAdder的值,相当于重新new了一个DoubleAdder
        doubleAdder.reset();
        //获取当前value的值并重置
        System.out.println(doubleAdder.sumThenReset());//0.0
        //doubleValue就是调用的doubleValue.sum()方法
        System.out.println(doubleAdder.doubleValue());//0.0
        /**
         * LongAccumulator提供了自定义的函数操作,通过lambda表达式
         */
        DoubleAccumulator doubleAccumulator = new DoubleAccumulator((x, y) -> x + y, 0);

        doubleAccumulator.accumulate(1.1d);
        doubleAccumulator.accumulate(2.2d);
        doubleAccumulator.accumulate(3.3d);
        System.out.println(doubleAccumulator.doubleValue());//6.6

        /**
         * LongAdder只能用来计算加法,且从零开始计算
         */
        LongAdder longAdder = new LongAdder();

        //加1
        longAdder.increment();//1
        longAdder.increment();//2
        longAdder.increment();//3
        //加指定值
        longAdder.add(2L);//5
        longAdder.add(2L);//7
        //减1
        longAdder.decrement();//6
        longAdder.decrement();//5
        //返回当前值,特别注意:在没有并发的情况下返回的准确值,在高并发情况下返回的不能保证准确性(瞬间值)
        System.out.println(longAdder.sum());//5
        //重置longAdder的值,相当于重新new了一个LongAdder
        longAdder.reset();
        //获取当前value的值并重置
        System.out.println(longAdder.sumThenReset());//0
        //longValue就是调用的longAdder.sum()方法
        System.out.println(longAdder.longValue());//0
    }

}

注意:sum()方法返回当前值,在没有并发的情况下返回的准确值,在高并发情况下返回的不能保证准确性(瞬间值)

阿里规范有一条:如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数) ,那么为什么要这么定义?

6、LongAdder性能对比

分别对比AtomicLong、LongAdder、LongAccumulator三者的性能,50个线程,每个线程100W次,总点赞数出来

/**
 * LongAdderCompared
 * 分别对比AtomicLong、LongAdder、LongAccumulator三者的性能
 * 案例:
 * 1、50个线程,每个线程100W次,总点赞数出来
 * 2、统计使用Synchronized、AtomicLong、LongAdder、LongAccumulator四种方式的耗时
 *
 */
public class LongAdderCompared {
    public static void main(String[] args) throws InterruptedException {
        ClickNumberNet clickNumberNet = new ClickNumberNet();
        long startTime;
        long endTime;
        CountDownLatch countDownLatch1 = new CountDownLatch(50);
        CountDownLatch countDownLatch2 = new CountDownLatch(50);
        CountDownLatch countDownLatch3 = new CountDownLatch(50);
        CountDownLatch countDownLatch4 = new CountDownLatch(50);

        //----------clickBy Synchronized----------
        startTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * 10000; j++) {
                        clickNumberNet.clickBySync();
                    }
                } finally {
                    countDownLatch1.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickBySync result: " + clickNumberNet.number);

        //----------clickByAtomicLong----------
        startTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * 10000; j++) {
                        clickNumberNet.clickByAtomicLong();
                    }
                } finally {
                    countDownLatch2.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByAtomicLong result: " + clickNumberNet.atomicLong);

        //----------clickByLongAdder----------
        startTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * 10000; j++) {
                        clickNumberNet.clickByLongAdder();
                    }
                } finally {
                    countDownLatch3.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAdder result: " + clickNumberNet.longAdder.sum());

        //----------clickByLongAccumulator----------
        startTime = System.currentTimeMillis();
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * 10000; j++) {
                        clickNumberNet.clickByLongAccumulator();
                    }
                } finally {
                    countDownLatch4.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAccumulator result: " + clickNumberNet.longAccumulator.longValue());
    }
    /**
     * 运行结果:
     * ----costTime: 2223 毫秒    clickBySync result: 50000000
     * ----costTime: 1000 毫秒    clickByAtomicLong result: 50000000
     * ----costTime: 181 毫秒     clickByLongAdder result: 50000000
     * ----costTime: 127 毫秒     clickByLongAccumulator result: 50000000
     *
     * 进程已结束,退出代码0
     */
}


/**
 * 点赞
 */
class ClickNumberNet {
    //初始点赞0
    int number = 0;
    AtomicLong atomicLong = new AtomicLong(0);
    LongAdder longAdder = new LongAdder();
    LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0);

    /**
     * 普通的synchronized同步加锁
     */
    public synchronized void clickBySync() {
        number++;
    }

    /**
     * AtomicLong原子加
     */
    public void clickByAtomicLong() {
        atomicLong.incrementAndGet();
    }

    /**
     * LongAdder加
     */
    public void clickByLongAdder() {
        longAdder.increment();
    }

    /**
     * longAccumulator加
     */
    public void clickByLongAccumulator() {
        longAccumulator.accumulate(1);
    }
}
LongAdder为什么快?

LongAdder类结构图:LongAdder是Striped64的子类
在这里插入图片描述

7、Striped64

  • Striped64有几个比较重要的成员函数:

    /** Number of CPUS, to place bound on table size        CPU数量,即cells数组的最大长度 */
    static final int NCPU = Runtime.getRuntime().availableProcessors();
    
    /**
     * Table of cells. When non-null, size is a power of 2.
      =* cells数组,为2的幂,2,4,8,16.....,方便以后位运算
     */
    transient volatile Cell[] cells;
    
    /**基础value值,当并发较低时,只累加该值主要用于没有竞争的情况,通过CAS更新。
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     */
    transient volatile long base;
    
    /**创建或者扩容Cells数组时使用的自旋锁变量调整单元格大小(扩容),创建单元格时使用的锁。
     * Spinlock (locked via CAS) used when resizing and/or creating Cells. 
     */
    transient volatile int cellsBusy;
    
  • Striped64中一些变量或者方法的定义

    base: 类似于AtomicLong中全局的value值。再没有竞争情况下数据直接累加到base上,或者cells扩容时,也需要将数据写入到base上
    collide:表示扩容意向,false一定不会扩容,true可能会扩容
    cellsBusy:初始化cells或者扩容cells需要获取锁,0表示无锁状态,1表示其他线程已经持有了锁
    casCellsBusy:通过CAS操作修改cellsBusy的值,CAS成功代表获取锁,返回true
    NCPU:当前计算机CPU数量,Cell数组扩容时会使用到
    getProbe( ):获取当前线程的hash值
    advanceProbe( ):重置当前线程的hash值
    
  • Cell:是 java.util.concurrent.atomic 下 Striped64 的一个内部类

/**
 * Padded variant of AtomicLong supporting only raw accesses plus CAS.
 *
 * JVM intrinsics note: It would be possible to use a release-only
 * form of CAS here, if it were provided.
 */
@sun.misc.Contended static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
    final boolean cas(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long valueOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> ak = Cell.class;
            valueOffset = UNSAFE.objectFieldOffset
                (ak.getDeclaredField("value"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

LongAdder为什么这么快?

一句话概括:

LongAdder无竞争的情况,跟AtomicLong一样,对同一个base进行操作,当出现竞争关系时则是采用化整为零的做法,以空间换时间,用一个数组cells,将一个value拆分进这个数组cells。多个线程需要同时对value进行操作时候,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果。

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

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

在这里插入图片描述

内部有一个base变量,一个Cell[]数组。

base变量:非竞态条件下,直接累加到该变量上

Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中

8、总结

AtomicLong:

  • 原理:CAS+自旋 + incrementAndGet
  • 使用场景:低并发下的全局计算
    AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。
  • 缺陷:高并发后性能急剧下降
    AtomicLong的自旋会成为瓶颈,N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。

LongAdder:

  • 原理:CAS+Base+Cell数组分散空间换时间并分散了热点数据
  • 使用场景:高并发下的全局计算
  • 缺陷:sum求和后还有计算线程修改结果的话,最后结果不够准确,sum执行时,并没有限制对base和cells的更新。所以LongAdder不是强一致性的,它是最终一致性的。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

King Gigi.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值