1、 原子类简介

原子类作用和锁类似,是为了保证线程安全,但是原子类相比于锁:

  • 粒度更细:原子变量可以把竞争范围缩小到变量级别,通常情况下,锁的粒度都要大于原子变量的粒度。
  • 效率更高:除了高度竞争的情况之外,使用原子类的效率通常会比使用同步互斥锁的效率更高,因为原子类底层利用了 CAS 操作,不会阻塞线程。

2、 六类原子类概览

类型 具体类
Atomic* 基本类型原子类 AtomicInteger、AtomicLong、AtomicBoolean
Atomic*Array 数组类型原子类 AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
Atomic*Reference 引用类型原子类 AtomicReference、AtomicStampedReference、AtomicMarkableReference
Atomic*FieldUpdater 升级类型原子类 AtomicIntegerfieldupdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
Adder 累加器 LongAdder、DoubleAdder
Accumulator 积累器 LongAccumulator、DoubleAccumulator

2.1、 基本类型原子类

2.1.1、AtomicInteger 类常用方法

  • 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) //如果输入的数值等于预期值,则以原子方式将该值更新为输入值

2.1.2、AtomicInteger 示例

  1. public class AtomicIntegerDemo implements Runnable{
  2. private static final AtomicInteger atomicInteger = new AtomicInteger();
  3. private static Integer basicInteger = 0;
  4. public void addAtomic(){
  5. atomicInteger.incrementAndGet();
  6. }
  7. public void addBasic(){
  8. basicInteger ++;
  9. }
  10. @Override
  11. public void run() {
  12. IntStream.range(0,1000).forEach(e -> {
  13. addAtomic();
  14. addBasic();
  15. });
  16. }
  17. public static void main(String[] args) throws InterruptedException {
  18. Runnable runnable = new AtomicIntegerDemo();
  19. Thread thread1 = new Thread(runnable);
  20. Thread thread2 = new Thread(runnable);
  21. thread1.start();
  22. thread2.start();
  23. thread1.join();
  24. thread2.join();
  25. System.out.println("原子类结果:" + atomicInteger.get());
  26. System.out.println("普通类结果:" + basicInteger);
  27. }
  28. }

输出

  1. 原子类结果:2000
  2. 普通类结果:1437

2.2、数组型原子类

即数组中每个对象都保证原子特性

2.3、 引用类型原子类

AtomicReference 类的作用和AtomicInteger 并没有本质区别, AtomicInteger 可以让一个整数保证原子性,而AtomicReference 可以让一个对象保证原子性

  1. public class SpinLock {
  2. private AtomicReference<Thread> sign = new AtomicReference<>();
  3. public void lock(){
  4. Thread current = Thread.currentThread();
  5. while(!sign.compareAndSet(null, current)){
  6. System.out.println("自旋尝试获取锁");
  7. }
  8. }
  9. public void unlock(){
  10. Thread current = Thread.currentThread();
  11. sign.compareAndSet(current, null);
  12. }
  13. public static void main(String[] args) {
  14. SpinLock spinLock = new SpinLock();
  15. Runnable runnable = new Runnable() {
  16. @Override
  17. public void run() {
  18. System.out.println(Thread.currentThread().getName() + "尝试获取自旋锁");
  19. spinLock.lock();
  20. System.out.println(Thread.currentThread().getName() + "成功获取自旋锁");
  21. try {
  22. Thread.sleep(300);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. } finally {
  26. spinLock.unlock();
  27. System.out.println(Thread.currentThread().getName() + "释放自旋锁");
  28. }
  29. }
  30. };
  31. new Thread(runnable).start();
  32. new Thread(runnable).start();
  33. }
  34. }

2.4、升级类型原子类

AtomicIntegerFieldUpdater对普通变量升级
使用场景:偶尔需要一个原子Get-Set操作

  1. public class AtomicUpdater implements Runnable{
  2. private static CountHandler countHandlerOne;
  3. private static CountHandler countHandlerTwo;
  4. private static AtomicIntegerFieldUpdater<CountHandler> countUpdater =
  5. AtomicIntegerFieldUpdater.newUpdater(CountHandler.class,"count");
  6. @Override
  7. public void run() {
  8. IntStream.range(0,1000).forEach(e -> {
  9. countHandlerOne.count ++;
  10. countUpdater.getAndIncrement(countHandlerTwo);
  11. });
  12. }
  13. @Data
  14. static class CountHandler{
  15. //不能是包装类 Integer
  16. volatile int count;
  17. }
  18. public static void main(String[] args) throws InterruptedException {
  19. countHandlerOne = new CountHandler();
  20. countHandlerTwo = new CountHandler();
  21. Runnable runnable = new AtomicUpdater();
  22. Thread thread1 = new Thread(runnable);
  23. Thread thread2 = new Thread(runnable);
  24. thread1.start();
  25. thread2.start();
  26. thread1.join();
  27. thread2.join();
  28. System.out.println("普通int结果:" + countHandlerOne.count);
  29. System.out.println("升级原子类结果:" + countHandlerTwo.count);
  30. }

输出

  1. 普通int结果:1918
  2. 升级原子类结果:2000

2.5、Adder 加法器

2.5.1、LongAdder和AtomicLong对比

  • 在并发程度不高时,LongAdder和AtomicLong性能差不多,高并发下 LongAdder 比 AtomicLong 效率更高,但是需要耗费更多空间。
  • LongAdder适合的场景时统计求和技术的场景,而LongAdder只提供了基本的add方法,AtomicLong 还具有CAS方法

对比LongAdder和AtomicLong耗时,发现高并发下 LongAdder 比 AtomicLong 效率更高

  1. public class LongAdderTest {
  2. static class TaskLongAdder implements Runnable{
  3. private LongAdder counter;
  4. public TaskLongAdder(LongAdder counter) {
  5. this.counter = counter;
  6. }
  7. @Override
  8. public void run() {
  9. IntStream.range(0,10000).forEach(e -> {
  10. counter.increment();
  11. });
  12. }
  13. }
  14. static class TaskAtomicLong implements Runnable{
  15. private AtomicLong counter;
  16. public TaskAtomicLong(AtomicLong counter) {
  17. this.counter = counter;
  18. }
  19. @Override
  20. public void run() {
  21. IntStream.range(0,10000).forEach(e -> {
  22. counter.getAndIncrement();
  23. });
  24. }
  25. }
  26. public static void main(String[] args) {
  27. LongAdder longAdder = new LongAdder();
  28. TaskLongAdder taskLongAdder = new TaskLongAdder(longAdder);
  29. ExecutorService executorService1 = Executors.newFixedThreadPool(20);
  30. Long start1 = System.currentTimeMillis();
  31. IntStream.range(0,2000).forEach(e-> executorService1.execute(taskLongAdder));
  32. executorService1.shutdown();
  33. while (!executorService1.isTerminated()){
  34. }
  35. Long end1 = System.currentTimeMillis();
  36. System.out.println("LongAdder耗时:" + (end1 - start1) + " 结果: " + longAdder.sum());
  37. AtomicLong atomicLong = new AtomicLong();
  38. TaskAtomicLong taskAtomicLong = new TaskAtomicLong(atomicLong);
  39. ExecutorService executorService2 = Executors.newFixedThreadPool(20);
  40. Long start2 = System.currentTimeMillis();
  41. IntStream.range(0,2000).forEach(e-> executorService2.execute(taskAtomicLong));
  42. executorService2.shutdown();
  43. while (!executorService2.isTerminated()){
  44. }
  45. Long end2 = System.currentTimeMillis();
  46. System.out.println("AtomicLong耗时:" + (end2 - start2) + " 结果: " + atomicLong.get());
  47. }
  48. }

输出

  1. LongAdder耗时:137 结果: 20000000
  2. AtomicLong耗时:367 结果: 20000000

2.5.2、AtomicLong和LongAdder原理刨析

AtomicLong原理

AtomicLong每次加法都要flush和refresh,导致浪费资源
image.png

LongAdder原理

LongAdder 引入了分段累加的概念,内部一共有两个参数参与计数:第一个叫作 base,它是一个变量,第二个是 Cell[] ,是一个数组。其中的 base 是用在竞争不激烈的情况下的,可以直接把累加结果改到 base 变量上。当竞争激烈时,各个线程会分散累加到自己所对应的那个 Cell[] 数组的某一个对象。LongAdder 会通过计算出每个线程的 hash 值来给线程分配到不同的 Cell 上去

执行 LongAdder.sum() 的时候,会把各个线程里的 Cell 累计求和,并加上 base,形成最终的总和
image.png

  1. public long sum() {
  2. Cell[] as = cells; Cell a;
  3. long sum = base;
  4. if (as != null) {
  5. for (int i = 0; i < as.length; ++i) {
  6. if ((a = as[i]) != null)
  7. sum += a.value;
  8. }
  9. }
  10. return sum;
  11. }

2.6、Accumulator 积累器

2.6.1、简介

Accumulator 和Adder非常相似,Accumulator 就是一个更通用版本的Adder

  1. public class LongAccumulatorDemo {
  2. public static void main(String[] args) {
  3. LongAccumulator accumulator = new LongAccumulator((x,y) -> x * y,1);
  4. ExecutorService executorService = Executors.newFixedThreadPool(20);
  5. IntStream.range(1,10).forEach(e-> executorService.execute(() -> accumulator.accumulate(e)));
  6. executorService.shutdown();
  7. while (!executorService.isTerminated()){
  8. }
  9. System.out.println("结果: " + accumulator.getThenReset());
  10. }
  11. }

输出

  1. 结果: 362880

2.6.2、适合场景

  • 适合数据量大时的多线程并行计算
  • 计算的顺序不会影响结果