Java-Atomic原子操作类详解及源码分析,Java原子操作类进阶,LongAdder源码分析
  o21g2AkC8e2r 2023年11月02日 24 0



文章目录

  • 一、Java原子操作类概述
  • 1、什么是原子操作类
  • 2、为什么要用原子操作类
  • 3、CAS入门
  • 二、基本类型原子类
  • 1、概述
  • 2、代码实例
  • 三、数组类型原子类
  • 1、概述
  • 2、代码实例
  • 四、引用类型原子类
  • 1、概述
  • 2、AtomicReference
  • 3、ABA问题与AtomicStampedReference
  • 4、一次性修改:AtomicMarkableReference
  • 五、对象属性修改原子类
  • 1、概述
  • 2、使用要求
  • 3、为什么要用对象属性修改原子类
  • 4、AtomicIntegerFieldUpdater使用实例
  • 5、AtomicReferenceFieldUpdater使用实例
  • 5、AtomicIntegerFieldUpdater与synchronized、AtomicInteger效率对比
  • 六、原子操作增强类
  • 1、概述
  • 2、LongAdder常用方法
  • 3、LongAdder使用实例
  • 4、LongAccumulator
  • 5、synchronized、AtomicLong、LongAdder、LongAccumulator性能对比
  • 七、LongAdder源码分析


一、Java原子操作类概述

1、什么是原子操作类

Java从JDK1.5开始提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。

Java-Atomic原子操作类详解及源码分析,Java原子操作类进阶,LongAdder源码分析_原子类

2、为什么要用原子操作类

《阿里巴巴Java开发手册中》:

【参考】 volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。
说明: 如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger();
count.addAndGet(1); 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。

在 Java 语言中,我们可以通过 synchronized 关键字或者锁(Lock)来实现部分的原子性控制。然而,这种方式可能会造成性能上的问题,特别是在高并发的情况下。因此,Java 提供了一系列的原子类(如 AtomicInteger,AtomicLong 等),这些类使用了硬件级别的原子操作指令,能够在无锁的情况下实现高效的原子操作。

3、CAS入门

我知道乐观锁,但是我的确不知道CAS啊,到底什么是CAS

总的来说,原子操作类是基于CAS实现的,这些类在内部使用了非阻塞算法和硬件级别的 CAS 操作(Compare and Swap,比较并交换),保证了并发环境下的原子性和高性能。

二、基本类型原子类

1、概述

基本类型原子类包括AtomicBoolean、AtomicInteger、AtomicLong,

主要有以下几个常用方法:

// 自动更新当前值与给定的功能应用到当前和给定值的结果,返回更新后的值。 
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) 
 
// 自动添加给定值并获取当前值。  
int addAndGet(int delta) 

// 自动设置的值来指定更新值,如果给定==期望值。  
boolean compareAndSet(int expect, int update) 

// 递减
int decrementAndGet() 

// 为扩大基本转换后的 double返回该 AtomicInteger值。  
double doubleValue() 

// 为扩大基本转换后的 float返回该 AtomicInteger值。  
float floatValue() 

// 获取当前值。  
int get() 

// 自动更新当前值与给定的功能应用到当前和给定值的结果,返回前一个值。  
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) 

// 自动添加给定值并获取当前值。  
int getAndAdd(int delta) 

// 获取并递减
int getAndDecrement() 

// 获取并递增
int getAndIncrement() 

// 获取值并设置新值
int getAndSet(int newValue) 

// 通过函数式接口更新值
int getAndUpdate(IntUnaryOperator updateFunction) 

// 递增并且获取
int incrementAndGet() 

// 作为一个 int返回该 AtomicInteger值。  
int intValue() 

// 最终设置为给定的值。 
void lazySet(int newValue) 
 
// 为扩大基本转换后的 long返回该 AtomicInteger值。  
long longValue() 

// 设置值
void set(int newValue) 

// 返回当前值的字符串表示形式。  
String toString() 

// 自动更新当前值与结果应用给定的函数,返回更新后的值。  
int updateAndGet(IntUnaryOperator updateFunction) 

// 自动设置的值来指定更新值,如果修改值==期望值。 
boolean weakCompareAndSet(int expect, int update)

2、代码实例

使用原子操作类,可以保证所有的操作都是原子操作,高并发下可以保证线程安全。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

class MyNumber
{
    AtomicInteger atomicInteger = new AtomicInteger();

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


public class AtomicIntegerDemo
{
    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();
        }
        //等待上面50个线程全部计算完成后,再去获得最终值

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+"\t"+"result: "+myNumber.atomicInteger.get());
    }
}

三、数组类型原子类

1、概述

数组类型原子类顾名思义,是基本类型与引用类型原子类的数组,包括:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray(引用类型的数组)。

主要常用方法:

// 使用将给定函数 更新i下标处的值为x ,并返回更新后的值
int accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction) 

// 下标i 的值加delta
int addAndGet(int i, int delta) 

// 下标i 的值如果等于期望值,就设置为update,返回是否更新成功
boolean compareAndSet(int i, int expect, int update) 

// 下标i 递减并返回
int decrementAndGet(int i) 

// 获取下标i 的值
int get(int i) 

// 获取值,并通过给定函数设置下标i 的值
int getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction) 

// 获取下标i的值,并加上给定的值
int getAndAdd(int i, int delta) 

// 获取下标i的值并减1
int getAndDecrement(int i) 

// 获取下标i的值,并加1
int getAndIncrement(int i) 

// 获取下标i的值,并设置为新值
int getAndSet(int i, int newValue) 
// 获取下标i的值,并通过函数设置新值 
int getAndUpdate(int i, IntUnaryOperator updateFunction) 

// 递增下标i的值并返回递增后的值 
int incrementAndGet(int i) 
// 懒加载
void lazySet(int i, int newValue) 
// 数组长度  
int length() 
// 设置下标i的值
void set(int i, int newValue) 

// 通过函数修改值,并且获取修改后的值
int updateAndGet(int i, IntUnaryOperator updateFunction) 

// 修改值
boolean weakCompareAndSet(int i, int expect, int update)

我们可以发现,数组类型原子类与基本类型原子类的方法大同小异,无非是可以自由的操作数组中某个下标的值。

2、代码实例

public class AtomicIntegerArrayDemo
{
    public static void main(String[] args)
    {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
        //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
        //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));
        }

        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));
    }
}

四、引用类型原子类

1、概述

引用类型原子类,就是可以操作自定义类型的原子类,包括:AtomicReference、AtomicStampedReference、AtomicMarkableReference。

其主要方法基本也都相似,此处就不一一举例了。

2、AtomicReference

以下实例中,使用AtomicReference实现了对User类型的原子操作:

import java.util.concurrent.atomic.AtomicReference;
/**
* 原子引用
* 如果想包装某个类,就用原子引用
*/
public class AtomicReferenceDemo {
 
    public static void main(String[] args) {
        User u1 = new User("zhangsan", 14);
        User u2 = new User("lisi", 15);
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(u1);
        // true 设置为lisi
        System.out.println(atomicReference.compareAndSet(u1, u2) + "当前值" +atomicReference.get().getName());
        // false 还是lisi
        System.out.println(atomicReference.compareAndSet(u1, u2) + "当前值" +atomicReference.get().getName());
    }
}
 
class User{
    private String name;
    private Integer age;
 
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
}

3、ABA问题与AtomicStampedReference

CAS会导致“ABA问题”。

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将值变成A。此时线程1进行CAS操作发现内存中仍然是A,然后线程1操作成功。

尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。

中间过程如果不介意别人动过,那无所谓。

中间过程别人不能动,那就有问题了。

// ABA问题的产生与解决
public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args)
    {
        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"首次版本号:"+stamp);

            //暂停500毫秒,保证后面的t4线程初始化拿到的版本号和我一样
            try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }

            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"2次流水号:"+stampedReference.getStamp());

            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"3次流水号:"+stampedReference.getStamp());

        },"t3").start();

        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"首次版本号:"+stamp);

            //暂停1秒钟线程,等待上面的t3线程,发生了ABA问题
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            boolean b = stampedReference.compareAndSet(100, 2022, stamp, stamp + 1);

            System.out.println(b+"\t"+stampedReference.getReference()+"\t"+stampedReference.getStamp());

        },"t4").start();

    }

	// ABA问题的产生
    private static void abaHappen()
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        new Thread(() -> {
            try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(atomicInteger.compareAndSet(100, 2022)+"\t"+atomicInteger.get());
        },"t2").start();
    }
}

以上实例,我们使用AtomicStampedReference,对每次操作都使用一个版本号来解决,并且每次修改数据将版本号+1,可以解决ABA问题。

4、一次性修改:AtomicMarkableReference

AtomicMarkableReference维护一个对象引用和一个标记位(true、false),该标记位可以通过原子方式进行更新。

类似于将AtomicStampedReference时间戳状态简化为了true、false,常用于对象初始化工作。

isMarked()方法返回当前的标记值(true、false);
关键方法 compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark),可以根据预期的标记值和新的标记值、预期的对象和新的对象,自由的设置值。

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);

            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
            System.out.println(Thread.currentThread().getName()+"\t"+"t2线程CASresult: "+b);
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.isMarked());
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference());
        },"t2").start();
    }
}

五、对象属性修改原子类

1、概述

对象属性修改原子类,就是用于直接修改对象的属性,以一种线程安全的方式操作非线程安全对象内的某些字段。

包含三个类:AtomicIntegerFieldUpdater(原子更新对象中int、Integer类型字段的值)、AtomicLongFieldUpdater(原子更新对象中Long、long类型字段的值)、AtomicReferenceFieldUpdater(原子更新引用类型字段的值)。

2、使用要求

更新的对象属性必须使用volatile修饰符,否则会报错:

Java-Atomic原子操作类详解及源码分析,Java原子操作类进阶,LongAdder源码分析_原子类_02


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

3、为什么要用对象属性修改原子类

比如以下实例,银行转账想要通过传统方式,只能使用synchronized或者Lock保证线程安全,而这种方式会使并发量急剧下降;又或者使用AtomicInteger原子类保证线程安全,但是该字段又无法通过JSON、MyBatis等数据库操作或者其他操作自动转换,此时就考虑使用AtomicIntegerFieldUpdater。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

class BankAccount//资源类
{
    String bankName = "CCB";

    private volatile int money = 0;//钱数

    public void add()
    {
        money++;
    }

    public int getMoney() {
        return money;
    }
}

/**
 * 以一种线程安全的方式操作非线程安全对象的某些字段。
 *
 * 需求:
 * 10个线程,
 * 每个线程转账1000
 */
public class AtomicIntegerFieldUpdaterDemo
{
    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 = 1; j <=1000; j++) {
                        bankAccount.add();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+"\t"+"result: "+bankAccount.getMoney());
    }
}

4、AtomicIntegerFieldUpdater使用实例

AtomicIntegerFieldUpdater的方法与上述原子引用类相似,此处不再详细解释。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

class BankAccount//资源类
{
    String bankName = "CCB";

    //更新的对象属性必须使用 volatile 修饰符。
    private volatile int money = 0;//钱数

    public void add()
    {
        money++;
    }

    public int getMoney() {
        return money;
    }
    //因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须
    // 使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。

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

    //不加synchronized,保证高性能原子性,局部微创小手术
    public void transMoney(BankAccount bankAccount)
    {
        fieldUpdater.getAndIncrement(bankAccount);
    }


}

/**
 * 以一种线程安全的方式操作非线程安全对象的某些字段。
 *
 * 需求:
 * 10个线程,
 * 每个线程转账1000,
 * 不使用synchronized,尝试使用AtomicIntegerFieldUpdater来实现。
 */
public class AtomicIntegerFieldUpdaterDemo
{
    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 = 1; j <=1000; j++) {
                        //bankAccount.add();
                        bankAccount.transMoney(bankAccount);
                    }
                } finally {
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+"\t"+"result: "+bankAccount.getMoney());
    }
}

5、AtomicReferenceFieldUpdater使用实例

AtomicReferenceFieldUpdater与AtomicIntegerFieldUpdater使用类似,AtomicReferenceFieldUpdater用于更新更复杂的属性。

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

class MyVar //资源类
{
    public volatile Boolean isInit = Boolean.FALSE;

    AtomicReferenceFieldUpdater<MyVar,Boolean> referenceFieldUpdater =
            AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");

    public void init(MyVar myVar)
    {
        if (referenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE))
        {
            System.out.println(Thread.currentThread().getName()+"\t"+"----- start init,need 2 seconds");
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            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 = 1; i <=5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            },String.valueOf(i)).start();
        }
    }
}

5、AtomicIntegerFieldUpdater与synchronized、AtomicInteger效率对比

我们简单使用一个小例子,通过结果我们发现,AtomicIntegerFieldUpdater方式性能比AtomicInteger稍差一些,但是远优于synchronized:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class Test {

    static class Bank {

        /**
         * 使用AtomicIntegerFieldUpdater
         */
        public volatile int fieldUpdaterMoney = 0;
        AtomicIntegerFieldUpdater<Test.Bank> fieldUpdater =
                AtomicIntegerFieldUpdater.newUpdater(Test.Bank.class,"fieldUpdaterMoney");
        public void fieldUpdaterMoney(Test.Bank bankAccount)
        {
            fieldUpdater.getAndIncrement(bankAccount);
        }

        /**
         * 使用synchronized
         */
        public int synchronizedMoney = 0;
        public synchronized void synchronizedMoney()
        {
            synchronizedMoney++;
        }

        /**
         * 使用AtomicInteger
         */
        AtomicInteger atomicIntegerMoney = new AtomicInteger(0);
        public void atomicIntegerMoney() {
            atomicIntegerMoney.getAndIncrement();
        }
    }

    public static final int threadNumber = 50;
    public static final int _1W = 10000;
    public static void main(String[] args) throws InterruptedException {
        long startTime;
        long endTime;

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

        Bank bank = new Bank();

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <=100 * _1W; j++) {
                        bank.synchronizedMoney();
                    }
                } finally {
                    countDownLatch1.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t synchronizedMoney: "+bank.synchronizedMoney);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <=100 * _1W; j++) {
                        bank.atomicIntegerMoney();
                    }
                } finally {
                    countDownLatch2.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t atomicIntegerMoney: "+bank.atomicIntegerMoney.get());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <=100 * _1W; j++) {
                        bank.fieldUpdaterMoney(bank);
                    }
                } finally {
                    countDownLatch3.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t fieldUpdaterMoney: "+bank.fieldUpdaterMoney);
    }
}

----costTime: 3513 毫秒 synchronizedMoney: 50000000
----costTime: 816 毫秒 atomicIntegerMoney: 50000000
----costTime: 1138 毫秒 fieldUpdaterMoney: 50000000

六、原子操作增强类

1、概述

原子操作增强类包含DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder。
LongAdder只能用于计算加法,且只能从0开始计算,LongAccumulator提供了自定义的函数操作。

《阿里巴巴Java开发手册中》:

【参考】 volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。
说明: 如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger();
count.addAndGet(1); 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。

在高并发下,推荐使用原子操作增强类,它提供了比原子操作类更好的性能。

2、LongAdder常用方法

LongAdder常用方法,DoubleAdder类似。

// 将当前的value加x
void add(long x)

// 将当前的value加1
void increment()

// 将当前value减1
void decrement()

// 返回当前值。特别注意,在没有并发更新value的情况下,sum会返回一个精确值,在存在并发的情况下,sum不包装返回精确值
long sum()

// 将value重置为0,可用于替代重新new一个LongAdder,但此方法只可以在没有并发更新的情况下使用。
void reset()

// 获取当前value,并将value重置为0
long sumThenReset()

3、LongAdder使用实例

LongAdder longAdder = new LongAdder();
// 高并发下线程安全
longAdder.increment();
longAdder.increment();
longAdder.increment();

System.out.println(longAdder.sum());

4、LongAccumulator

LongAdder只能用于计算加法,且只能从0开始计算,LongAccumulator提供了自定义的函数操作。

LongAccumulator构造方法需要传入一个函数:

public LongAccumulator(LongBinaryOperator accumulatorFunction,
                       long identity) {
    this.function = accumulatorFunction;
    base = this.identity = identity;
}

LongBinaryOperator需要传入一个left、一个right,并且有一个返回值:

@FunctionalInterface
public interface LongBinaryOperator {

    long applyAsLong(long left, long right);
}

LongAccumulator的accumulate方法,用于将传入的参数与当前的参数,使用函数进行计算。

LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator()
{
    @Override
    public long applyAsLong(long left, long right)
    {
        return left + right;
    }
},0);

longAccumulator.accumulate(1);//0 + 1 = 1
longAccumulator.accumulate(3);//1 + 3 = 4

System.out.println(longAccumulator.get());

5、synchronized、AtomicLong、LongAdder、LongAccumulator性能对比

通过结果我们可以发现,LongAdder、LongAccumulator在高并发场景下,有着极高的性能,数倍于AtomicLong。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

class ClickNumber //资源类
{
    int number = 0;
    public synchronized void clickBySynchronized()
    {
        number++;
    }

    AtomicLong atomicLong = new AtomicLong(0);
    public void clickByAtomicLong()
    {
        atomicLong.getAndIncrement();
    }

    LongAdder longAdder = new LongAdder();
    public void clickByLongAdder()
    {
        longAdder.increment();
    }

    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();
        long startTime;
        long endTime;

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

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

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


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

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

    }
}

----costTime: 4059 毫秒 clickBySynchronized: 50000000
----costTime: 716 毫秒 clickByAtomicLong: 50000000
----costTime: 73 毫秒 clickByLongAdder: 50000000
----costTime: 57 毫秒 clickByLongAccumulator: 50000000

七、LongAdder源码分析

LongAdder为什么在高并发下保持良好性能?LongAdder源码详细分析


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
o21g2AkC8e2r