你使用过 Java 中的哪些原子类?
你使用过 Java 中的哪些原子类?
回答重点
Java 中的原子类是通过使用硬件提供的原子操作指令(如 CAS,Compare-And-Swap)来确保操作的原子性,从而避免线程竞争问题。
常用的原子类有以下几种:
- AtomicInteger:用于操作整数的原子类,提供了原子性的自增、自减、加法等操作。
- AtomicLong:与
AtomicInteger类似,但用于操作long型数据。 - AtomicBoolean:用于操作布尔值的原子类,提供了原子性的布尔值比较和设置操作。
- AtomicReference:用于操作对象引用的原子类,支持对引用对象的原子更新。
- AtomicStampedReference:在
AtomicReference的基础上,增加了时间戳或版本号的比较,避免了 ABA 问题。 - AtomicIntegerArray 和 AtomicLongArray:分别是
AtomicInteger和AtomicLong的数组版本,提供了对数组中各个元素的原子操作。
扩展知识
原子类可以分为五大类
画个脑图汇总一下:
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 修饰来保证可见性,看下源码,很直观。
累加器
上述的都是更新数据,而 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 的一个特例。