你使用过 Java 中的哪些原子类?

Sherwin.Wei Lv7

你使用过 Java 中的哪些原子类?

回答重点

Java 中的原子类是通过使用硬件提供的原子操作指令(如 CAS,Compare-And-Swap)来确保操作的原子性,从而避免线程竞争问题。

常用的原子类有以下几种:

  1. AtomicInteger:用于操作整数的原子类,提供了原子性的自增、自减、加法等操作。
  2. AtomicLong:与 AtomicInteger 类似,但用于操作 long 型数据。
  3. AtomicBoolean:用于操作布尔值的原子类,提供了原子性的布尔值比较和设置操作。
  4. AtomicReference:用于操作对象引用的原子类,支持对引用对象的原子更新。
  5. AtomicStampedReference:在 AtomicReference 的基础上,增加了时间戳或版本号的比较,避免了 ABA 问题。
  6. AtomicIntegerArrayAtomicLongArray:分别是 AtomicIntegerAtomicLong 的数组版本,提供了对数组中各个元素的原子操作。

扩展知识

原子类可以分为五大类

画个脑图汇总一下:

image-20210303202253776.png

CAS 简单的理解为:给予一个共享变量的内存地址和内存中应该的值(预期值)和新值,然后通过一条 CPU 指令来比较此内存地址上的值是否等于预期值,如果是则替换内存地址上的值为新值,如果不是则不予替换且返回。

也就是说硬件层面支持一条指令来实现这么几个操作,一条指令是不会被打断的,所以保证了原子性。

基本类型

可以简单的理解为通过基本类型原子类 AtomicBoolean、AtomicInteger 和 AtomicLong 就可线程安全地、原子地更新这几个基本类型。

数组类型

AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray,简单的理解为可以原子化地更新数组内的每个元素,几个的差别无非就是数组里面存储的数据是什么类型。

引用类型

AtomicReference、AtomicStampedReference 和 AtomicMarkableReference,就是对象引用的原子化更新。

差别在于 AtomicStampedReference 和 AtomicMarkableReference 可以避免 CAS 的 ABA 问题。

AtomicStampedReference 是通过版本号 stamp 来避免, AtomicMarkableReference 是通过一个布尔值 mark 来避免。

ABA 问题

因为 CAS 是将期望值和当时内存地址上的值进行对比,假设期望值是 1 ,地址上的值现在是 1,只不过中间被人改成了 2 ,然后又改回了1,所以此时你 CAS 操作去对比是可以替换的,你无法得知中间值是否改过,这种情况就叫 ABA 问题。

而解决 ABA 问题的做法就是用版本号,每次修改版本就+1,这样即使值是一样的但是版本不同,就能得知之前被改过了。

属性更新类型

AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater,是通过反射,原子化的更新对象的属性,不过要求属性必须用 volatile 修饰来保证可见性,看下源码,很直观。

image-20210303202310901.png

累加器

上述的都是更新数据,而 DoubleAccumulator、DoubleAdder、LongAccumulator 和 LongAdder 主要用来累加数据。

首先 AtomicLong 也能累加,而 LongAdder 是专业累加,也只能累加,并发度更高,它通过分多个 cells 来减少线程的竞争,提高了并发度。

你可以理解为如果拿 AtomicLong 是实现累加就是一本本子,然后 20 个人要让本子上累加计数。

而 LongAdder 分了 10 个本子,20个人可以分别拿这 10 个本子来计数(减少了竞争,提高了并发度),然后最后的结果再由 10个本子上的数相加即可。

xxxAccumulator 和 xxxAdder 两者的区别?

xxxAccumulator 的功能比 xxxAdder 丰富,可以自定义累加方法,也可以设置初始值,按照注释上的解释 xxxAdder 等价于 new xxxAccumulator((x, y) -> x + y, 0L}。

所以可以说 xxxAdder 是 xxxAccumulator 的一个特例。

CAS

Comments