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

news2024/11/15 12:25:45

文章目录

  • 一、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包,这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。
在这里插入图片描述

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修饰符,否则会报错:
在这里插入图片描述
因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法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源码详细分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1080971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot (1)

目录 1 入门案例 1.1 环境准备 1.2 编写pom.xml 1.3 编写入口程序 1.4 编写接口 1.5 编写配置 1.6 快速部署 1.6.1 打jar包 1.6.2 部署 1.7 访问接口 2 全注解开发 2.1 常用注解 2.2 属性绑定注解 2.2.1 注册组件 2.2.2 ConfigurationProperties(prefix"te…

AlphaPose Pytorch 代码详解(一):predict

前言 代码地址&#xff1a;AlphaPose-Pytorch版 本文以图像 1.jpg&#xff08;854x480&#xff09;为例对整个预测过程的各个细节进行解读并记录 python demo.py --indir examples/demo --outdir examples/res --save_img1. YOLO 1.1 图像预处理 cv2读取BGR图像 img [480,…

哈希的应用--位图和布隆过滤器

哈希的应用--位图和布隆过滤器 位图1. 位图概念2. 位图在实际中的应用3. 位图相似应用给定100亿个整数&#xff0c;如何找到只出现一次的整数&#xff1f;1个文件100亿int&#xff0c;1G内存&#xff0c;如何找到不超过2次的所有整数 布隆过滤器1. 布隆过滤器的提出2. 布隆过滤…

HarmonyOS学习 -- ArkTS开发语言入门

文章目录 一、编程语言介绍二、TypeScript基础类型1. 布尔值2. 数字3. 字符串4. 数组5. 元组6. 枚举7. unknown8. void9. null 和 undefined10. 联合类型 三、TypeScript基础知识条件语句if语句switch语句 函数定义有名函数和匿名函数可选参数剩余参数箭头函数 类1. 类的定义2.…

华为认证 | 这门HCIA认证正式发布!

华为认证云计算工程师HCIA-Cloud Computing V5.5&#xff08;中文版&#xff09;自2023年9月28日起&#xff0c;正式在中国区发布。 01 发布概述 基于“平台生态”战略&#xff0c;围绕“云-管-端”协同的新ICT技术架构&#xff0c;华为公司打造了覆盖ICT领域的认证体系&#…

机器人制作开源方案 | 齿轮传动轴偏心轮摇杆简易四足

1. 功能描述 齿轮传动轴偏心轮摇杆简易四足机器人是一种基于齿轮传动和偏心轮摇杆原理的简易四足机器人。它的设计原理通常如下&#xff1a; ① 齿轮传动&#xff1a;通过不同大小的齿轮传动&#xff0c;实现机器人四条腿的运动。通常采用轮式齿轮传动或者行星齿轮传动&#xf…

git多分支、git远程仓库、ssh方式连接远程仓库、协同开发(避免冲突)、解决协同冲突(多人在同一分支开发、 合并分支)

1 git多分支 2 git远程仓库 2.1 普通开发者&#xff0c;使用流程 3 ssh方式连接远程仓库 4 协同开发 4.1 避免冲突 4.2 协同开发 5 解决协同冲突 5.1 多人在同一分支开发 5.2 合并分支 1 git多分支 ## 命令操作分支-1 创建分支git branch dev-2 查看分支git branch-3 分…

bash一行输入,多行回显demo脚本

效果图&#xff1a; 脚本&#xff1a; #!/bin/bash # 定义一个变量&#xff0c;用来存储输入的内容 input"" # 定义一个变量&#xff0c;用来存储输入的字符 char""# 为了让read能读到空格键 IFS_store$IFS IFS# 提示内容&#xff0c;在while循环中也有&a…

SMOS数据处理,投影变换,‘EPSG:6933‘转为‘EPSG:4326‘

在处理SMOS数据时&#xff0c;遇到了读取nc数据并存为tif后&#xff0c;影像投影无法改变&#xff0c;因此全球数据无法重叠。源数据的投影为EPSG:6933&#xff0c;希望转为EPSG:4326。 解决代码。 python import os import netCDF4 as nc import numpy as np from osgeo impo…

阿里云ModelScope 是一个“模型即服务”(MaaS)平台

简介 项目地址&#xff1a;https://github.com/modelscope/modelscope/tree/master ModelScope 是一个“模型即服务”(MaaS)平台&#xff0c;旨在汇集来自AI社区的最先进的机器学习模型&#xff0c;并简化在实际应用中使用AI模型的流程。ModelScope库使开发人员能够通过丰富的…

sap 一次性供应商 供应商账户组 临时供应商 <转载>

原文链接&#xff1a;https://blog.csdn.net/xianshengsun/article/details/132620593 sap中有一次性供应商这个名词&#xff0c;一次性供应商和非一次性供应商又有什么区别呢&#xff1f; 有如何区分一次性供应商和非一次性供应商呢&#xff1f; 1 区分一次性供应商和非一次性…

狄拉克函数及其性质

狄拉克函数及其性质 狄拉克函数 近似处理 逼近近似 积分近似 狄拉克函数的性质 狄拉克函数的Hermite展开

【C++】【自用】STL六大组件:算法

文章目录 &#x1f53a;sortstable_sort&#x1f53a;reverse&#x1f53a;swap&#x1f53a;find&#x1f53a;max/min&#x1f53a;next_permutation/prev_permutation 全排列binary_searchlower_bound/upper_bound 求下界和上界set_union/set_intersection/set_difference 求…

结构体课程自我理解

目录 1. 结构体类型的声明 1.2特殊的声明方法 1.3结构体的自引用 1.4 typedef 对结构体命名 2. 结构体变量的创建和初始化 3. 结构成员访问操作符 4. 结构体内存对⻬ 4.1下面给大伙4个练习题&#xff0c;有自我解析 4.2为什么存在内存对齐 4.3修改默认对齐数 5. …

Centos中利用自带的定时器Crontab_实现mysql数据库自动备份_linux中mysql自动备份脚本---Linux运维工作笔记056

这个经常需要,怕出问题因而需要经常备份数据库,可以利用centos自带的定时器,配合脚本实现自动备份. 1.首先查看一下,这个crontab服务有没有打开: 执行:ntsysv 可以看到已经开机自启动了. 注意这个操作界面,用鼠标不行,需要用,tab按键,直接tab到确定,或取消,然后按回车回到命…

如何下不可选中的文章

背景&#xff1a; 看到了一篇比较有用的微信公众号文章&#xff08;这个文章应该是跳转到了公众号外的网站的 url 了&#xff09;&#xff0c;想留档&#xff0c;但是手机选中不了。但是这个事情作为程序员&#xff0c;怎么能束手呢。 操作&#xff1a; 1、将微信公众号链接在…

Copa:无需重建镜像,直接修补容器漏洞

关注【云原生百宝箱】公众号&#xff0c;与你一起探讨应用迁移&#xff0c;GitOps&#xff0c;二次开发&#xff0c;解决方案&#xff0c;CNCF生态。 copa 是一个使用 Go 编写的 CLI 工具&#xff0c;基于 buildkit&#xff0c;可以根据像 Trivy 这样的流行工具的漏洞扫描结果直…

LeetCode-343-整数拆分

题目描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 题目链接&#xff1a; LeetCode-343-整数拆分 解题思路&#xff1a; 还是根据动规五…

Centos (含Rocky-Linux) VSFTPD 简单设置

本文并非深入讨论vsftp配置的文章&#xff0c;仅以能连通为目的&#xff0c;适合那些临时需要上传点东西到服务器的场景。 一、安装 dnf -y updatednf -y install vsftpdsystemctl start vsftpdsystemctl enable vsftpd二、防火墙 开放21端口&#xff1a; firewall-cmd --zo…

利用正则表达式进行数据采集和处理

目录 一、正则表达式的概述 二、正则表达式在数据采集中的运用 1、匹配和提取数据 2、数据清洗 3、数据验证 三、Python中的re模块介绍 1、re.match()方法 2、re.search()方法 总结 正则表达式是一种强大的文本处理工具&#xff0c;它可以用于模式匹配、提取、替换等操…