1. 什么是原子类?
- 一个操作是不可分割的,即便是在多线程的情况下也可以保证;
- 作用和锁类似,保证并发安全,但是原子类的粒度更细,锁是锁住好几行代码,原子类锁的是这个变量;
- 通常情况了,原子类比锁效率更高,除了高度竞争的情况;
原子类 | 类型 |
---|---|
基本类型原子类 | AtomicInteger、AtomicLong、AtomicBoolean |
数组类型原子类 | AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray |
引用类型原子类 | AtomicReference、AtomicStampedReference、AtomicMarkableReference |
升级类型原子类 | AtomicIntegerFieldupdater、AtomicLongFieldupdater、AtomicReferenceFieldupdater |
累计类型原子类 | LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator |
2. 基本类型原子类,以AtomicInteger为例
- AtomicInteger常用方法:get()、getAndSet(int newValue)、getAndIncrement()、getAndDecrement()、getAndAdd(int delta)、compareAndSet(int expect, int update)。
- AtomicInteger使用示例:
public class AtomicIntegerDemo1 implements Runnable {
private static final AtomicInteger atomicInteger = new AtomicInteger();
public void increAtomic() {
atomicInteger.getAndIncrement();
}
private static volatile int count = 0;
public void increCount() {
count++;
}
public static void main(String[] args) throws InterruptedException {
AtomicIntegerDemo1 r = new AtomicIntegerDemo1();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("原子类:" + atomicInteger);
System.out.println("非原子类:" + count);
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increAtomic();
increCount();
}
}
}
原子类:20000
非原子类:17820
2. Adder累加器
- 是Java8引入的,比较新的一个类;
- 高并发下的LongAdder比AtomicLong效率高,不过本质是空间换时间;
- 竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性;
- 演示高并发场景下,LongAdder比AtomicLong性能好:
public class AtomicLongDemo implements Runnable {
private static AtomicLong counter = new AtomicLong(0);
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
counter.incrementAndGet();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
service.submit(new AtomicLongDemo());
}
service.shutdown();
while (!service.isTerminated()){
}
long end = System.currentTimeMillis();
System.out.println(counter.get());
System.out.println("耗时:" + (end - start));
}
}
100000000
耗时:1718
public class LongAdderDemo implements Runnable {
private static LongAdder counter = new LongAdder();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
service.submit(new LongAdderDemo());
}
service.shutdown();
while (!service.isTerminated()){
}
long end = System.currentTimeMillis();
System.out.println(counter.sum());
System.out.println("耗时:" + (end - start));
}
}
100000000
耗时:212
-
多线程情况下AtomicLong的性能不如LongAdder,AtomicLong每一次加法的结果,都要刷到到主内存和且其他线程要更新到线程工作内存,即每次加法都需要同步,导致很耗费资源;
-
LongAdder每个线程会有自己的计数器,仅仅用来在自己线程内计数,这样一来就不会和其他线程额计数器干扰,线程之间不存在竞争关系,在加法过程中,根本不需要同步,这里也没有一个公共的counter来给所有线程同一计数;
-
LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计数,在竞争不激烈的情况下,直接累加到base变量上,在竞争积累时候,利用Cell[]数据,每个线程分散累加到自己的Cell中,每个线程都是一个独立的计数器,在自加的过程中就提高了效率,其本质就是用空间换时间;
-
sum源码分析:
首先有一个Cell数组,然后定义了一个base,当数组为空的时候,直接返回base,当数组不为空的时候,则遍历这个数组,从base开始逐个累加起来,由于求和时没加锁,所以求和过程中不是线程安全的,结果可能会不精确。 -
LongAdder适合于统计计数求和的场景,而且LongAdder之提供了add方法,而AtomicLong还具有cas方法。
3. Accumulator累加器
public class LongAccumulatorDemo implements Runnable {
private static LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
@Override
public void run() {
for (int i = 0; i < 10; i++) {
longAccumulator.accumulate(i);
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(8);
LongAccumulatorDemo longAccumulatorDemo = new LongAccumulatorDemo();
for (int i = 0; i < 10; i++) {
executorService.submit(longAccumulatorDemo);
}
executorService.shutdown();
while (!executorService.isTerminated()) {
}
System.out.println(longAccumulator.getThenReset());
}
}
450
- Accumulator累加器适用于大量的并行计算的场景,且不对顺序有要求