当前位置: 首页 > news >正文

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

文章目录

  • 一、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 设置为lisiSystem.out.println(atomicReference.compareAndSet(u1, u2) + "当前值" +atomicReference.get().getName());// false 还是lisiSystem.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标识,都是falsetry { 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()
{@Overridepublic long applyAsLong(long left, long right){return left + right;}
},0);longAccumulator.accumulate(1);//0 + 1 = 1
longAccumulator.accumulate(3);//1 + 3 = 4System.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源码详细分析

相关文章:

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

文章目录 一、Java原子操作类概述1、什么是原子操作类2、为什么要用原子操作类3、CAS入门 二、基本类型原子类1、概述2、代码实例 三、数组类型原子类1、概述2、代码实例 四、引用类型原子类1、概述2、AtomicReference3、ABA问题与AtomicStampedReference4、一次性修改&#xf…...

算法通过村第十二关-字符串|黄金笔记|冲刺难题

文章目录 前言最长公共前缀纵向比较横向比较 字符串压缩问题表示数值的字符串总结 前言 提示&#xff1a;我有时候在想&#xff0c;我是真的不太需要其他人&#xff0c;还是因为跟他们在一起时没法自己&#xff0c;所以才保持距离。我们的交谈就像是平行而毫无交集的自言自语。…...

3ds Max渲染太慢?创意云“一键云渲染”提升3ds Max渲染体验

&#xfeff;在数字艺术设计领域&#xff0c;3ds Max是广泛使用的三维建模和渲染软件之一。然而&#xff0c;许多用户都面临着一个共同的问题&#xff1a;渲染速度太慢。渲染一帧画面需要耗费数小时&#xff0c;让人无法忍受。除了之前给大家介绍的几种解决方法外&#xff1a; …...

记录一次公益SRC的常见的cookie注入漏洞(适合初学者)

目录 谷歌语法-信息收集 cookie注入 实战演示 信息收集 SQL注入判断 查找字段数 爆破表名 输出结果 总结 hack渗透视频教程&#xff0c;扫码免费领 谷歌语法-信息收集 1.查找带有ID传参的网站&#xff08;可以查找sql注入漏洞&#xff09; inurl:asp idxx 2.查找网站后…...

[ACTF2020 新生赛]Exec1

拿到题目&#xff0c;不知道是sql注入还是命令执行漏洞 先ping一下主机 有回显&#xff0c;说明是命令执行漏洞 我们尝试去查看目录 127.0.0.1|ls&#xff0c;发现有回显&#xff0c;目录下面有个index.php的文件 我们之间访问index.php 输入127.0.0.1;cat index.php 发现又…...

DeepFace【部署 03】轻量级人脸识别和面部属性分析框架deepface在Linux环境下服务部署(conda虚拟环境+docker)

Linux环境下服务部署 1.使用虚拟环境[810ms]1.1 环境部署1.2 服务启动 2.使用Docker[680ms] 1.使用虚拟环境[810ms] 1.1 环境部署 Anaconda的安装步骤这里不再介绍&#xff0c;直接开始使用。 # 1.创建虚拟环境 conda create -n deepface python3.9.18# 2.激活虚拟环境 cond…...

vuex的求和案例和mapstate,mapmutations,mapgetters

main.js import Vue from vue import App from ./App.vue //引入vuex import Vuex from vuex //引入store import store from ./store/indexVue.config.productionTip falsenew Vue({el:"#app",render: h > h(App),store,beforeCreate(){Vue.prototype.$bus th…...

Docker 网络访问原理解密

How Container Networking Works: Practical Explanation 这篇文章讲得非常棒&#xff0c;把docker network讲得非常清晰。 分为三个部分&#xff1a; 1&#xff09;docker 内部容器互联。 2&#xff09;docker 容器 访问 外部root 网络空间。 3&#xff09;外部网络空间…...

统信UOS离线安装nginx

注意&#xff1a;安装之前一定要切换到开发者模式&#xff0c;不然会提示没有权限 1 安装所依赖的包 gcc gcc-c openssl PCRE zlib 我平时有一个gcc的包&#xff0c;我会直接把里面的包全部安装一遍,下面是地址 链接: https://pan.baidu.com/s/1Ty35uQx_7iliduohkuNWPQ?pw…...

机器学习基础-手写数字识别

手写数字识别&#xff0c;计算机视觉领域的Hello World利用MNIST数据集&#xff0c;55000训练集&#xff0c;5000验证集。Pytorch实现神经网络手写数字识别感知机与神经元、权重和偏置、神经网络、输入层、隐藏层、输出层mac gpu的使用本节就是对Pytorch可以做的事情有个直观的…...

idea 插件推荐(持续更新)

文章目录 Material Theme UIcodeium(建议有梯子的使用)Key Promoter XCodeGlanceRainbow BracketsMarkdown NavigatorRestfulToolkitString Manipulation Material Theme UI 谁不想拥有一款狂拽炫酷 吊炸天 的编码主题呢,给你推荐Material Theme UI Plugin Material Theme UI是…...

实现Promise所有核心功能和方法

一直以来对Promise只是会用简单的方法&#xff0c;例如then&#xff0c;catch等&#xff0c;对于其余各种方法也只是简单了解&#xff0c;这次想要通过实现Promise来加深对Promise的使用 话不多说&#xff0c;直接开始&#xff0c;简单粗暴一步步来 一&#xff1a;了解Promise …...

学习总结1

Vue的学习 Vue是一套用于构建用户界面的渐进式JavaScript框架&#xff1b; Vue中关键的几个概念&#xff1a;组件化&#xff0c;MVVM&#xff0c;响应式&#xff0c;和生命周期。 1. 组件化&#xff1a; 在Vue框架中&#xff0c;允许你将界面拆分为小的&#xff0c;独立的可…...

使用 Apache Camel 和 Quarkus 的微服务(二)

【squids.cn】 全网zui低价RDS&#xff0c;免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 在本系列的第一部分&#xff0c;我们看到了一个简化版的基于微服务的转账应用程序&#xff0c;该应用程序使用Apache Camel和AWS SDK&#xff08;软件开发套件&#xf…...

pid-limit参数实验

fork炸弹命令 :(){ :|:& };: 可以看到&#xff0c;如果docker没有限制&#xff0c;会遭到fork炸弹恶意 参考 https://www.cyberciti.biz/faq/understanding-bash-fork-bomb/...

jvm--执行引擎

文章目录 1. 执行引擎的工作流程2. 解释器、JIT及时编译器3. 热点代码及探测技术4. HotSpotVM 中 JIT 分类 执行引擎属于 JVM 的下层&#xff0c;里面包括解释器、及时编译器、垃圾回收器 JVM 的主要任务是负责 装载字节码到其内部&#xff0c;但字节码并不能够直接运行在操作…...

day13|二叉树理论

一、二叉树的定义 //定义一个二叉树&#xff1a;使用链式存储 public class TreeNode {int val; // 节点的值TreeNode left; // 左子节点TreeNode right; // 右子节点public TreeNode() {}// 构造函数&#xff0c;初始化节点值public TreeNode(int val){this.valval;}// 构造函…...

php+html+js+ajax实现文件上传

phphtmljsajax实现文件上传 目录 一、表单单文件上传 1、上传页面 2、接受文件上传php 二、表单多文件上传 1、上传页面 2、接受文件上传php 三、表单异步xhr文件上传 1、上传页面 2、接受文件上传php 四、表单异步ajax文件上传 1、上传页面 2、接受文件上传ph…...

日期时间参数,格式配置(SpringBoot)

介绍 在SpringBoot项目中&#xff0c;接口中的日期和时间类型的参数&#xff0c;配置格式。 日期格式 接口中常用的日期时间格式有两种&#xff1a; 字符串&#xff08;比如&#xff1a;yyyy-MM-dd HH:mm:ss&#xff09;时间戳&#xff08;比如&#xff1a;1696839876955&a…...

JAVA 泛型的定义以及使用

泛型类 /*** <T> 为该类定义泛型&#xff0c;可以是一个或多个<T,...>* 定义的泛型可以在类中作为&#xff1a;* 类变量类型&#xff1a; T data* 类方法的入参以及返回类型 public void setData(T data)&#xff0c;public T getData();次数以set&a…...

Day-08 基于 Docker安装 Nginx 镜像-负载均衡

1、反向代理后&#xff0c;自然而然就引出了负载均衡,下面简单实现负载均衡的效果; 2、实现该效果需要再添加一个 Nginx &#xff0c;所以要增加一个文件夹。 /home|---mutou|----nginx|----conf.d|----html|----conf.d2|----html3 1.创建 html3 文件夹&#xff0c; 新建 index…...

3、在 CentOS 8 系统上安装 PostgreSQL 15.4

PostgreSQL&#xff0c;作为一款备受欢迎的开源关系数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;已经存在了三十多年的历史。它提供了SQL语言支持&#xff0c;用于管理数据库和执行CRUD操作&#xff08;创建、读取、更新、删除&#xff09;。 由于其卓越的健壮性…...

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

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

总结html5中常见的选择器

HTML5并没有引入新的选择器类型&#xff0c;它仍然使用CSS选择器来选择和操作HTML元素。HTML5中仍然可以使用CSS2和CSS3中定义的各种选择器。以下是HTML5中常见的选择器类型&#xff1a; 1. 元素选择器&#xff08;Element Selector&#xff09;&#xff1a;使用元素名称作为选…...

Java基础面试-JDK JRE JVM

详细解释 JDK&#xff08;Java Devalpment Kit&#xff09;java 开发工具 JDK是Java开发工具包&#xff0c;它是Java开发者用于编写、编译、调试和运行Java程序的核心组件。JDK包含了Java编程语言的开发工具和工具集&#xff0c;以及Java标准库和其他一些必要的文件。JDK中的…...

OpenCV实现图像傅里叶变换

傅里叶变换 dftcv.dft(img_float32,flagscv.DFT_COMPLEX_OUTPUT): flags:标志位&#xff0c;指定变换类型&#xff0c;cv.DFT_COMPLEX_OUTPUT会返回复数结果。 傅立叶变换&#xff0c;将输入的图像从空间域转换到频率域。 返回结果: 此函数返回一个复杂数值数组&#xff0c…...

快手新版本sig3参数算法还原

Frida Native层主动调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81…...

Linux 安全 - LSM机制

文章目录 前言一、LSM起源二、LSM简介2.1 MAC2.2 LSM特征 三、Major and Minor LSMs3.1 Major LSMs3.2 Minor LSMs3.3 BPF LSM 四、LSM 框架五、LSM Capabilities Module六、LSM hooks 说明参考资料 前言 在这两篇文章中介绍了 Linux 安全机制 Credentials &#xff1a; Linu…...

uni-app:实现简易自定义下拉列表

效果 代码 <template><view><view class"dropdown-trigger" tap"showDropdown">{{ selectedItem }}</view><view class"dropdown-list" v-if"showList"><view class"dropdown-item" v-f…...

排序算法——直接插入排序

一、介绍 插入排序就是将前两个元素排好&#xff0c;再将第三个元素通过与前边的元素比较后插入适当的位置&#xff0c;再将第四个元素插入&#xff0c;不断重复插入与前边元素比较的操作&#xff0c;直到将元素都排列好。 演示如下&#xff1a; 视频演示&#xff1a;…...