概述
JUC下的atomic打头的原子类相信大家并不陌生,温习下API,用代码证明阿里手册为啥建议使用LongAdder,它为什么就快!!!
- AtomicBoolean
- AtomicInteger
- AtomicIntegerArray
- AtomicIntegerFieldUpdater
- AtomicLong
- AtomicLongArray
- AtomicLongFieldUpdater
- AtomicMarkableReference
- AtomicReference
- AtomicReferenceArray
- AtomicReferenceFieldUpdater
- AtomicStampedReference
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
- Striped64
- Number
基本类型原子类
3个:AtomicInteger、AtomicBoolean、AtomicLong
常用API介绍:
public final int get()
: 获取当前的值
public final int getAndSet(int newValue)
:获取当前的值,并设置新的值
public final int getAndIncrement()
:获取当前的值,并自增
public final int getAndDecrement()
:获取当前的值,并自减
public final int getAndAdd(int delta)
:获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update)
:如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
示例代码:
import lombok.Getter;
import lombok.SneakyThrows;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* AtomicIntegerDemo
* 基本类型原子类case:AtomicInteger、AtomicBoolean、AtomicLong
*
* @author lcry
* @date 2021/09/24 21:50
*/
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));
System.out.println("获取当前值并自增:" + atomicInteger.getAndIncrement());
System.out.println("获取当前值并自减:" + atomicInteger.getAndDecrement());
System.out.println("获取当前值并加上期望值:" + atomicInteger.getAndAdd(10));
System.out.println("如果输入的数值等于预期值,则以原子方式将该值设置为输入值:" + atomicInteger.compareAndSet(12, 2021));
System.out.println("--------------");
System.out.println("初始值:" + atomicBoolean.get());
atomicBoolean.set(Boolean.TRUE);
System.out.println(atomicBoolean.getAndSet(Boolean.FALSE));
System.out.println(atomicBoolean.compareAndSet(Boolean.FALSE, Boolean.TRUE));
System.out.println("--------------");
System.out.println("初始值:" + atomicLong.get());
System.out.println("获取当前值并设置新值:" + atomicLong.getAndSet(2L));
System.out.println("获取当前值并自增:" + atomicLong.getAndIncrement());
System.out.println("获取当前值并自减:" + atomicLong.getAndDecrement());
System.out.println("获取当前值并加上期望值:" + atomicLong.getAndAdd(10L));
System.out.println("如果输入的数值等于预期值,则以原子方式将该值设置为输入值:" + atomicLong.compareAndSet(12L, 2021L));
}
/**
* 综合案例,多线程累加
*/
@SneakyThrows
private static void m1() {
MyNumber myNumber = new MyNumber();
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 1; i <= 100; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 5000; j++) {
myNumber.addPlusPlus();
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(myNumber.getAtomicInteger().get());
}
}
class MyNumber {
@Getter
private final AtomicInteger atomicInteger = new AtomicInteger();
public void addPlusPlus() {
atomicInteger.incrementAndGet();
}
}
数组类型原子类
3个:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
参考基本类型原子类,示例代码如下:
/**
* AtomicIntegerArrayDemo
* 数组类型原子类case:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
*
* @author lcry
* @date 2021/09/24 22:09
*/
public class AtomicIntegerArrayDemo {
//初始化5个容量的数组,默认值都是0
private static final AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
//初始10个容量的数组
private static final AtomicLongArray atomicLongArray = new AtomicLongArray(10);
//初始3个person
private static final AtomicReferenceArray<Person> atomicReferenceArray = new AtomicReferenceArray<>(3);
public static void main(String[] args) {
System.out.println("初始数据:" + atomicIntegerArray);
atomicIntegerArray.set(0, 0);
atomicIntegerArray.set(1, 1);
System.out.println("数组长度:" + atomicIntegerArray.length());
System.out.println("修改数组下标我为0个元素:" + atomicIntegerArray.compareAndSet(0, 0, 10));
System.out.println("下标0元素值为:" + atomicIntegerArray.get(0));
System.out.println("下标4元素值为:" + atomicIntegerArray.get(4));
System.out.println("--------");
System.out.println("初始数据:" + atomicLongArray);
atomicIntegerArray.set(0, 0);
atomicIntegerArray.set(1, 1);
System.out.println("数组长度:" + atomicLongArray.length());
System.out.println("修改数组下标我为0个元素:" + atomicLongArray.compareAndSet(0, 0, 10));
System.out.println("下标0元素值为:" + atomicLongArray.get(0));
System.out.println("下标4元素值为:" + atomicLongArray.get(4));
System.out.println("--------");
System.out.println("初始数据:" + atomicReferenceArray);
Person p1 = new Person("张三", 10);
atomicReferenceArray.set(0, p1);
atomicReferenceArray.set(1, new Person("李四", 20));
System.out.println("获取并设置: " + atomicReferenceArray.getAndSet(2, new Person("王五", 30)));
System.out.println("对比并设置" + atomicReferenceArray.compareAndSet(0, p1, new Person("张三", 50)));
System.out.println("最终数据:" + atomicReferenceArray);
}
}
@Data
@AllArgsConstructor
class Person {
private String name;
private Integer age;
}
引用类型原子类
3个:AtomicReference、AtomicStampedReference、AtomicMarkableReference
还记得前面写的解决ABA问题吗?
/**
* ABAProblemDemo
* 演示CAS引发的ABA问题以及解决方案
*
* @author lcry
* @date 2021/09/24 21:13
*/
public class ABAProblemDemo {
static AtomicInteger atomicInteger = new AtomicInteger(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100, false);
static CountDownLatch countDownLatch = new CountDownLatch(2);
@SneakyThrows
public static void main(String[] args) {
System.out.println("============以下是ABA问题的解决=============================");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "t 首次版本号:" + stamp);//1
//暂停一会儿线程,
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "t 2次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "t 3次版本号:" + atomicStampedReference.getStamp());
countDownLatch.countDown();
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "t 首次版本号:" + stamp);//1
//暂停一会儿线程,获得初始值100和初始版本号1,故意暂停3秒钟让t3线程完成一次ABA操作产生问题
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2021, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "t" + result + "t" + atomicStampedReference.getReference());
countDownLatch.countDown();
}, "t4").start();
countDownLatch.await();
System.out.println("============AtomicMarkableReference不关心引用变量更改过几次,只关心是否更改过======================");
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "t 1次版本号" + marked);
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
markableReference.compareAndSet(100, 101, marked, !marked);
System.out.println(Thread.currentThread().getName() + "t 2次版本号" + markableReference.isMarked());
markableReference.compareAndSet(101, 100, markableReference.isMarked(), !markableReference.isMarked());
System.out.println(Thread.currentThread().getName() + "t 3次版本号" + markableReference.isMarked());
}, "t5").start();
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "t 1次版本号" + marked);
//暂停几秒钟线程
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
markableReference.compareAndSet(100, 2021, marked, !marked);
System.out.println(Thread.currentThread().getName() + "t" + markableReference.getReference() + "t" + markableReference.isMarked());
}, "t6").start();
}
/**
* aba问题浮现
*/
private static void aba() {
new Thread(() -> {
atomicInteger.compareAndSet(100, 101);
atomicInteger.compareAndSet(101, 100);
}, "t1").start();
new Thread(() -> {
//暂停一会儿线程
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(100, 2021) + "t" + atomicInteger.get());
}, "t2").start();
//暂停一会儿线程,main彻底等待上面的ABA出现演示完成。
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
AtomicStampedReference:携带版本号的引用类型原子类,可以解决ABA问题,状态戳原子引用解决修改过几次
AtomicMarkableReference:原子更新带有标记位的引用类型对象,它的定义就是将状态戳简化为true|false,类似一次性筷子,不关心引用变量更改过几次,只关心是否更改过
对象的属性修改原子类
3个:AtomicIntegerFieldUpdater(原子更新对象中int类型字段的值)、AtomicLongFieldUpdater(原子更新对象中Long类型字段的值)、AtomicReferenceFieldUpdater(原子更新引用类型字段的值)
使用目的:以一种线程安全的方式操作非线程安全对象内的某些字段
使用要求:
- 更新的对象属性必须使用 public volatile 修饰符。
- 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
示例代码:
/**
* AtomicIntegerFieldUpdaterDemo
* 以一种线程安全的方式操作非线程安全对象的某些字段。
* 需求:
* 1000个人同时向一个账号转账一元钱,那么累计应该增加1000元,
* 除了synchronized和CAS,还可以使用AtomicIntegerFieldUpdater来实现。
*
* @author lcry
* @date 2021/09/24 22:37
*/
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);
}, String.valueOf(i)).start();
}
//暂停毫秒
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(bankAccount.money);
}
}
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
* 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
*
* @author lcry
* @date 2021/09/24 22:40
*/
public class AtomicReferenceFieldUpdaterDemo {
public static void main(String[] args) throws InterruptedException {
MyVar myVar = new MyVar();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
myVar.init(myVar);
}, String.valueOf(i)).start();
}
}
}
class MyVar {
static final AtomicReferenceFieldUpdater<MyVar, Boolean> atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
//默认未初始化
public volatile Boolean isInit = Boolean.FALSE;
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" + "------其它线程正在初始化");
}
}
}
原子操作增强类原理深度解析
4个:DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder
常用API介绍
import java.math.BigDecimal;
import java.util.concurrent.atomic.DoubleAccumulator;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.DoubleBinaryOperator;
import java.util.function.LongBinaryOperator;
/**
* LongAdderAPI
* LongAdder常用API
* LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator
*
* @author lcry
* @date 2021/09/25 10:46
*/
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());
//重置doubleAdder的值,相当于重新new了一个DoubleAdder
doubleAdder.reset();
//获取当前value的值并重置
System.out.println(doubleAdder.sumThenReset());
//longValue就是调用的longAdder.sum()方法
System.out.println(doubleAdder.doubleValue());
//LongAccumulator提供了自定义的函数操作
DoubleAccumulator doubleAccumulator = new DoubleAccumulator(new DoubleBinaryOperator() {
//相加
@Override
public double applyAsDouble(double left, double right) {
BigDecimal bd1 = new BigDecimal(Double.toString(left));
BigDecimal bd2 = new BigDecimal(Double.toString(right));
return bd1.add(bd2).doubleValue();
}
}, 100d);
doubleAccumulator.accumulate(1.1d);
doubleAccumulator.accumulate(2.2d);
doubleAccumulator.accumulate(3.3d);
System.out.println(doubleAccumulator.doubleValue());
}
private static void longAPI() {
//LongAdder只能用来计算加法,且从零开始计算
LongAdder longAdder = new LongAdder();
//加1
longAdder.increment();
longAdder.increment();
longAdder.increment();
//加指定值
longAdder.add(2L);
longAdder.add(2L);
//减1
longAdder.decrement();
longAdder.decrement();
//返回当前值,特别注意:在没有并发的情况下返回的准确值,在高并发情况下返回的不能保证准确性(瞬间值)
System.out.println(longAdder.sum());
//重置longAdder的值,相当于重新new了一个LongAdder
longAdder.reset();
//获取当前value的值并重置
System.out.println(longAdder.sumThenReset());
//longValue就是调用的longAdder.sum()方法
System.out.println(longAdder.longValue());
//LongAccumulator提供了自定义的函数操作
LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator() {
//实现前面的数减第二个数
@Override
public long applyAsLong(long left, long right) {
return left - right;
}
}, 100);
longAccumulator.accumulate(1);//100-1 = 99
longAccumulator.accumulate(2);//99-2 = 97
longAccumulator.accumulate(3);//97-3 = 94
System.out.println(longAccumulator.longValue());
}
}
注意:sum()方法返回当前值,在没有并发的情况下返回的准确值,在高并发情况下返回的不能保证准确性(瞬间值)
阿里规范有一条:如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)
,那么为什么要这么定义?
LongAdder性能对比
分别对比AtomicLong、LongAdder、LongAccumulator三者的性能,50个线程,每个线程100W次,总点赞数出来
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
/**
* LongAdderCompared
* 分别对比AtomicLong、LongAdder、LongAccumulator三者的性能
* 案例:
* 1、50个线程,每个线程100W次,总点赞数出来
* 2、统计使用AtomicLong、LongAdder、LongAccumulator三种方式的耗时
*
* @author lcry
* @date 2021/09/25 11:03
*/
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);
//----------clickBySync----------
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());
}
}
/**
* 点赞
*/
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);
}
}
执行结果:
----costTime: 1917 毫秒 clickBySync result: 50000000
----costTime: 1159 毫秒 clickByAtomicLong result: 50000000
----costTime: 140 毫秒 clickByLongAdder result: 50000000
----costTime: 111 毫秒 clickByLongAccumulator result: 50000000
LongAdder为什么快?
LongAdder类结构图:LongAdder是Striped64的子类
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 的一个内部类
abstract class Striped64 extends Number { @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]中
总结
- AtomicLong:
- 原理:CAS+自旋 + incrementAndGet
- 使用场景:
- 低并发下的全局计算
- AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。
- 缺陷:
- 高并发后性能急剧下降
- AtomicLong的自旋会成为瓶颈,N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。
- LongAdder:
- 原理:CAS+Base+Cell数组分散,空间换时间并分散了热点数据
- 使用场景:高并发下的全局计算
- 缺陷:
- sum求和后还有计算线程修改结果的话,最后结果不够准确,sum执行时,并没有限制对base和cells的更新(一句要命的话)。所以LongAdder不是强一致性的,它是最终一致性的。
性能对比可以参考下老外的一篇博文: LongAdder vs AtomicLong Performance
最后
以上就是细心小鸭子为你收集整理的JUC atomic原子操作类之18罗汉增强温习的全部内容,希望文章能够帮你解决JUC atomic原子操作类之18罗汉增强温习所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复