Java中的原子操作类
在Java中,原子操作类位于java.util.concurrent.atomic包中,这些类提供了一种简单、高效、线程安全的方式来更新一个变量,而不需要使用synchronized关键字进行同步。原子操作类通过利用底层的硬件支持(如CAS指令,即Compare-And-Swap)来确保操作的原子性。
一些原子操作类的异同
AtomicBoolean、AtomicInteger、AtomicLong、AtomicArray(例如AtomicIntegerArray、AtomicLongArray等)以及LongAdder的实现原理在核心思想上是相似的,但也有一些关键的区别。以下是对它们实现原理的详细分析:
-
AtomicBoolean、AtomicInteger、AtomicLong:
-
相同点:
- 都使用了CAS(Compare-And-Swap)操作来确保对值的更新是原子的。
- 内部值(对于AtomicBoolean是boolean类型,对于AtomicInteger是int类型,对于AtomicLong是long类型)通常都被volatile关键字修饰,以确保内存可见性。
- 都提供了一系列原子操作方法,如get()、set()、compareAndSet()等。
-
不同点:
- 主要区别在于它们处理的数据类型不同(boolean、int、long)。
- 在某些体系结构中,特别是32位系统,处理64位的数据类型(如long)可能需要特殊的处理来确保操作的原子性。
-
-
AtomicArray(如AtomicIntegerArray、AtomicLongArray等):
- AtomicArray类是对数组进行原子操作的封装。
- 实现原理也是基于CAS操作,但针对数组中的每一个元素。
- 这类原子数组类提供了对数组元素进行原子性更新和读取的方法。
-
LongAdder:
- LongAdder的实现原理与上述原子类有所不同,特别是在处理高并发场景下的性能问题。
- LongAdder内部维护了一个Cells数组,每个Cell里面有一个初始值为0的long型变量。
- 当多个线程尝试更新同一个值时,它们会被分散到不同的Cell上进行操作,从而减少了线程间的竞争,提高了并发性能。
- 在读取总和时,LongAdder会遍历所有的Cell并将它们的值相加。
AtomicBoolean、AtomicInteger、AtomicLong主要通过CAS操作和volatile关键字实现原子操作,区别主要在于处理的数据类型。
AtomicArray类对数组进行原子操作,也是基于CAS操作,但作用于数组元素。
LongAdder则采用了一种分段更新的策略,通过内部维护的Cells数组来减少线程间的竞争,提高并发更新的效率。
AtomicReference实现原理与其他原子操作类也类似。
AtomicInteger
AtomicInteger的实现原理主要基于CAS(Compare-And-Swap)操作,这是一种无锁化的算法,它通过比较和替换内存中的值来确保操作的原子性。
-
原子性保证: AtomicInteger通过CAS操作实现原子性。CAS包括三个操作数 —— 内存位置(V)、期望的原值(A)和新值(B)。这个操作的功能是,当内存位置V的值等于A时,将内存位置V的值更新为B。否则,不执行更新。这一过程是原子的,不会被其他线程打断。
-
无锁设计: AtomicInteger避免了传统的锁机制,减少了线程间的竞争和上下文切换的开销,从而提高了并发性能。
-
内部状态: AtomicInteger内部使用一个被
volatile
修饰的int
变量来存储整数值。volatile
关键字确保了变量的可见性,使得对该变量的读写操作具有原子性。
源码及释义
AtomicInteger关键部分的源码及解释:
|
|
字段
-
unsafe
字段- 释义:
Unsafe
类的实例,提供了直接操作内存的低级方法,是实现CAS操作的关键。 - 实现原理:
Unsafe
类是一个在sun.misc
包下的类,它提供了很多直接操作内存、线程等底层操作的方法。在AtomicInteger
中,它主要被用于执行CAS操作。
- 释义:
-
valueOffset
字段- 释义:表示
value
字段在AtomicInteger
对象内存布局中的偏移量。 - 实现原理:在静态代码块中,通过
Unsafe
类的objectFieldOffset
方法获取value
字段的偏移量。这个偏移量用于后续的CAS操作,以确定需要更新的内存位置。
- 释义:表示
-
value
字段- 释义:存储实际的整数值,是
AtomicInteger
封装的核心数据。 - 实现原理:使用
volatile
关键字修饰,确保该变量的可见性。当一个线程修改了这个值,其他线程能够立即看到这个修改。这是实现线程安全的关键之一。
- 释义:存储实际的整数值,是
方法
-
构造函数
- 释义:用于初始化
AtomicInteger
对象,并设置初始值。 - 实现原理:在创建
AtomicInteger
对象时,通过构造函数传入初始值,并赋值给value
字段。
- 释义:用于初始化
-
incrementAndGet()
方法- 释义:原子性地增加当前值并返回新值。
- 实现原理:通过
Unsafe
类的getAndAddInt
方法实现。这个方法会原子性地执行以下操作:首先获取当前value
的值,然后将其加1,最后返回加1后的新值。整个过程是原子的,不会被其他线程打断。
-
其他原子操作方法
decrementAndGet()
:原子性地减少当前值并返回新值。实现原理与incrementAndGet()
类似,只是将加1操作改为减1。getAndSet(int newValue)
:原子性地设置新值并返回旧值。通过CAS操作实现,先获取当前值作为旧值返回,然后设置新值。compareAndSet(int expect, int update)
:如果当前值等于期望值,则原子性地更新为给定的新值。这是CAS操作的典型应用,它比较内存中的值与期望值是否相等,如果相等则更新为新值。
AtomicInteger
类的实现原理主要基于CAS操作和volatile
关键字的可见性保证。通过CAS操作确保对整数值的修改是原子性的,避免了竞态条件和数据不一致等问题。而volatile
关键字则确保了变量的可见性,使得对该变量的读写操作具有原子性。这些机制共同作用,使得AtomicInteger
能够在多线程环境下安全地进行原子操作。
此外,AtomicInteger还提供了其他原子操作方法,例如decrementAndGet()
用于原子地减少值,getAndSet()
用于原子地设置新值并返回旧值,compareAndSet()
用于原子地比较并设置值等。这些方法都依赖于Unsafe类提供的CAS操作来确保操作的原子性。
LongAdder
是 Java 8 中引入的一个高性能的并发计数器,用于解决在多线程环境下 AtomicLong
由于大量的 CAS (Compare-And-Swap) 操作导致的性能瓶颈。LongAdder
通过分散热点更新,减少线程间的竞争,从而提高并发性能。
LongAdder
LongAdder
的核心思想是将并发更新的压力分散到多个内部变量(称为“cells”)上,而不是像 AtomicLong
那样所有线程都竞争同一个变量的更新。每个线程会尝试更新它自己的 cell,如果没有则尝试创建或找到一个未使用的 cell。在读取总和时,LongAdder
会将所有 cell 的值累加起来。
核心字段
-
cells
: 这是一个懒初始化的线程安全的数组,用于存储各个 cell 的值。 -
base
: 当没有竞争或者 cells 数组还未初始化时,所有的更新都会先更新到这个base
字段。 -
cellsBusy
: 用于在创建或扩展 cells 数组时实现同步的锁状态标志。 -
cellValues
: 一个用于缓存 cells 数组中值的临时数组,以提高求和操作的效率。 -
nCells
: 表示 cells 数组的长度。 -
padding
: 为了减少伪共享(false sharing)而填充的字节,以提高性能。
核心方法及源代码释义
构造函数
|
|
- 构造函数很简单,没有参数,也不执行任何操作,因为
LongAdder
使用了延迟初始化的策略。
Add 方法
|
|
add
方法首先尝试更新base
字段,如果base
更新失败(说明存在竞争),则尝试更新一个 cell。- 如果没有可用的 cell 或者更新 cell 失败,将调用
longAccumulate
方法来处理竞争情况。
LongAccumulate 方法
|
|
- 这是一个内部辅助方法,用于处理在
add
方法中遇到的竞争情况。 - 它会尝试找到一个空闲的 cell 来更新,或者在没有空闲 cell 时扩展 cells 数组。
Sum 方法
|
|
sum
方法返回当前LongAdder
的总和。- 它首先将
base
的值加到sum
上,然后遍历所有的 cell,将它们的值累加到sum
。 - 此方法的性能不高,慎用。
Reset 方法
|
|
reset
方法将LongAdder
的值重置为 0。- 它首先将
base
设置为 0,然后遍历所有的 cell,将它们也重置为 0。
思想精髓
- 减少竞争:通过将更新分散到多个 cell 上,
LongAdder
减少了线程间的竞争,从而提高了并发更新的效率。 - 延迟初始化:cells 数组是懒初始化的,只有当存在竞争时才会创建,这有助于节省内存和提高性能。
- 扩展性:当现有的 cells 数组不足以处理并发更新时,
LongAdder
会尝试扩展数组,以适应更多的线程。 - 缓存行填充:为了减少伪共享,
LongAdder
使用了填充字段来确保每个 cell 占据单独的缓存行。
LongAdder
通过其独特的设计提供了高性能的并发计数功能,特别是在高并发场景下表现优异。
AtomicReference
AtomicReference
是 Java 并发包 java.util.concurrent.atomic
中的一个类,它提供了一个可以用于原子更新引用类型变量的工具。与 AtomicInteger
、AtomicLong
等类似,AtomicReference
也利用了 CAS (Compare-And-Swap) 操作来确保更新的原子性。不过,与数值类型的原子类不同,AtomicReference
可以用于任何引用类型的对象。
AtomicReference
的实现原理主要依赖于底层的硬件支持,特别是 CAS (Compare-And-Swap) 操作。
核心方法
get()
: 获取当前引用的值。set(V newValue)
: 设置新的引用值。compareAndSet(V expect, V update)
: 如果当前值等于预期值,则将该值设置为给定的更新值。getAndSet(V newValue)
: 设置新的引用值,并返回旧值。
源码及注释
由于 AtomicReference
的源码较长,这里只列出部分核心方法和关键部分的源码,并添加注释。
|
|
value
:这是一个volatile
类型的变量,用于存储AtomicReference
的当前值。volatile
关键字确保了变量的内存可见性,使得所有线程都能看到最新的值。get()
:简单地返回当前引用的值。set(V newValue)
:直接设置新的引用值,不涉及任何比较或同步操作。compareAndSet(V expect, V update)
:使用 CAS 操作来原子地更新引用值。如果当前值(value
)等于预期值(expect
),则将其设置为更新值(update
)。这个方法返回一个布尔值,表示操作是否成功。getAndSet(V newValue)
:原子地设置新的引用值,并返回旧值。这是通过调用unsafe.getAndSetObject
方法实现的,该方法内部使用了 CAS 操作来确保原子性。
上述代码中的 unsafe
和 valueOffset
是 AtomicReference
内部使用的变量,用于通过 Unsafe 类直接操作内存。这些变量和相关方法没有在上述代码片段中显示,因为它们涉及到更底层的 JVM 和硬件操作。在实际应用中,我们通常不需要直接使用这些底层功能,而是通过 AtomicReference
提供的高级方法来操作引用值。