Java中的原子操作类

注意
本文最后更新于 2023-08-22,文中内容可能已过时。

在Java中,原子操作类位于java.util.concurrent.atomic包中,这些类提供了一种简单、高效、线程安全的方式来更新一个变量,而不需要使用synchronized关键字进行同步。原子操作类通过利用底层的硬件支持(如CAS指令,即Compare-And-Swap)来确保操作的原子性。

一些原子操作类的异同

AtomicBoolean、AtomicInteger、AtomicLong、AtomicArray(例如AtomicIntegerArray、AtomicLongArray等)以及LongAdder的实现原理在核心思想上是相似的,但也有一些关键的区别。以下是对它们实现原理的详细分析:

  1. AtomicBoolean、AtomicInteger、AtomicLong

    • 相同点

      • 都使用了CAS(Compare-And-Swap)操作来确保对值的更新是原子的。
      • 内部值(对于AtomicBoolean是boolean类型,对于AtomicInteger是int类型,对于AtomicLong是long类型)通常都被volatile关键字修饰,以确保内存可见性。
      • 都提供了一系列原子操作方法,如get()、set()、compareAndSet()等。
    • 不同点

      • 主要区别在于它们处理的数据类型不同(boolean、int、long)。
      • 在某些体系结构中,特别是32位系统,处理64位的数据类型(如long)可能需要特殊的处理来确保操作的原子性。
  2. AtomicArray(如AtomicIntegerArray、AtomicLongArray等)

    • AtomicArray类是对数组进行原子操作的封装。
    • 实现原理也是基于CAS操作,但针对数组中的每一个元素。
    • 这类原子数组类提供了对数组元素进行原子性更新和读取的方法。
  3. 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关键部分的源码及解释:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // Unsafe类的实例,用于执行CAS操作
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // value字段的内存偏移量
    private static final long valueOffset;

    static {
        try {
            // 获取value字段在对象中的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    // 实际的整数值,使用volatile修饰以保证可见性
    private volatile int value;

    // 构造函数
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    // 原子性地增加当前值并返回新值
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    // ... 其他方法如decrementAndGet(), getAndSet(), compareAndSet()等
}

字段

  1. unsafe字段

    • 释义Unsafe类的实例,提供了直接操作内存的低级方法,是实现CAS操作的关键。
    • 实现原理Unsafe类是一个在sun.misc包下的类,它提供了很多直接操作内存、线程等底层操作的方法。在AtomicInteger中,它主要被用于执行CAS操作。
  2. valueOffset字段

    • 释义:表示value字段在AtomicInteger对象内存布局中的偏移量。
    • 实现原理:在静态代码块中,通过Unsafe类的objectFieldOffset方法获取value字段的偏移量。这个偏移量用于后续的CAS操作,以确定需要更新的内存位置。
  3. value字段

    • 释义:存储实际的整数值,是AtomicInteger封装的核心数据。
    • 实现原理:使用volatile关键字修饰,确保该变量的可见性。当一个线程修改了这个值,其他线程能够立即看到这个修改。这是实现线程安全的关键之一。

方法

  1. 构造函数

    • 释义:用于初始化AtomicInteger对象,并设置初始值。
    • 实现原理:在创建AtomicInteger对象时,通过构造函数传入初始值,并赋值给value字段。
  2. incrementAndGet()方法

    • 释义:原子性地增加当前值并返回新值。
    • 实现原理:通过Unsafe类的getAndAddInt方法实现。这个方法会原子性地执行以下操作:首先获取当前value的值,然后将其加1,最后返回加1后的新值。整个过程是原子的,不会被其他线程打断。
  3. 其他原子操作方法

    • 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 的值累加起来。

核心字段

  1. cells: 这是一个懒初始化的线程安全的数组,用于存储各个 cell 的值。

  2. base: 当没有竞争或者 cells 数组还未初始化时,所有的更新都会先更新到这个 base 字段。

  3. cellsBusy: 用于在创建或扩展 cells 数组时实现同步的锁状态标志。

  4. cellValues: 一个用于缓存 cells 数组中值的临时数组,以提高求和操作的效率。

  5. nCells: 表示 cells 数组的长度。

  6. padding: 为了减少伪共享(false sharing)而填充的字节,以提高性能。

核心方法及源代码释义

构造函数

1
2
public LongAdder() {
}
  • 构造函数很简单,没有参数,也不执行任何操作,因为 LongAdder 使用了延迟初始化的策略。

Add 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}
  • add 方法首先尝试更新 base 字段,如果 base 更新失败(说明存在竞争),则尝试更新一个 cell。
  • 如果没有可用的 cell 或者更新 cell 失败,将调用 longAccumulate 方法来处理竞争情况。

LongAccumulate 方法

1
2
3
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
    // ... (省略部分代码)
}
  • 这是一个内部辅助方法,用于处理在 add 方法中遇到的竞争情况。
  • 它会尝试找到一个空闲的 cell 来更新,或者在没有空闲 cell 时扩展 cells 数组。

Sum 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public long sum() {
    Cell[] as = cells; Cell a;
    long sum = base;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}
  • sum 方法返回当前 LongAdder 的总和。
  • 它首先将 base 的值加到 sum 上,然后遍历所有的 cell,将它们的值累加到 sum
  • 此方法的性能不高,慎用。

Reset 方法

1
2
3
4
5
6
7
8
9
public void reset() {
    Cell[] as = cells;
    base = 0L;
    if (as != null) {
        for (Cell a : as)
            if (a != null)
                a.reset();
    }
}
  • reset 方法将 LongAdder 的值重置为 0。
  • 它首先将 base 设置为 0,然后遍历所有的 cell,将它们也重置为 0。

思想精髓

  • 减少竞争:通过将更新分散到多个 cell 上,LongAdder 减少了线程间的竞争,从而提高了并发更新的效率。
  • 延迟初始化:cells 数组是懒初始化的,只有当存在竞争时才会创建,这有助于节省内存和提高性能。
  • 扩展性:当现有的 cells 数组不足以处理并发更新时,LongAdder 会尝试扩展数组,以适应更多的线程。
  • 缓存行填充:为了减少伪共享,LongAdder 使用了填充字段来确保每个 cell 占据单独的缓存行。

LongAdder 通过其独特的设计提供了高性能的并发计数功能,特别是在高并发场景下表现优异。

AtomicReference

AtomicReference 是 Java 并发包 java.util.concurrent.atomic 中的一个类,它提供了一个可以用于原子更新引用类型变量的工具。与 AtomicIntegerAtomicLong 等类似,AtomicReference 也利用了 CAS (Compare-And-Swap) 操作来确保更新的原子性。不过,与数值类型的原子类不同,AtomicReference 可以用于任何引用类型的对象。

AtomicReference 的实现原理主要依赖于底层的硬件支持,特别是 CAS (Compare-And-Swap) 操作。

核心方法

  1. get(): 获取当前引用的值。
  2. set(V newValue): 设置新的引用值。
  3. compareAndSet(V expect, V update): 如果当前值等于预期值,则将该值设置为给定的更新值。
  4. getAndSet(V newValue): 设置新的引用值,并返回旧值。

源码及注释

由于 AtomicReference 的源码较长,这里只列出部分核心方法和关键部分的源码,并添加注释。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class AtomicReference<V> {
    private volatile V value; // 使用 volatile 关键字确保内存可见性

    public AtomicReference(V initialValue) {
        value = initialValue;
    }

    public AtomicReference() {
    }

    // 获取当前引用的值
    public final V get() {
        return value;
    }

    // 设置新的引用值
    public final void set(V newValue) {
        value = newValue;
    }

    // 如果当前值等于预期值,则将该值设置为给定的更新值
    public final boolean compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

    // 设置新的引用值,并返回旧值
    public final V getAndSet(V newValue) {
        return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
    }

    // ... 其他方法(如 lazySet, weakCompareAndSet 等)和内部变量(如 unsafe, valueOffset) ...
}
  • value:这是一个 volatile 类型的变量,用于存储 AtomicReference 的当前值。volatile 关键字确保了变量的内存可见性,使得所有线程都能看到最新的值。
  • get():简单地返回当前引用的值。
  • set(V newValue):直接设置新的引用值,不涉及任何比较或同步操作。
  • compareAndSet(V expect, V update):使用 CAS 操作来原子地更新引用值。如果当前值(value)等于预期值(expect),则将其设置为更新值(update)。这个方法返回一个布尔值,表示操作是否成功。
  • getAndSet(V newValue):原子地设置新的引用值,并返回旧值。这是通过调用 unsafe.getAndSetObject 方法实现的,该方法内部使用了 CAS 操作来确保原子性。

上述代码中的 unsafevalueOffsetAtomicReference 内部使用的变量,用于通过 Unsafe 类直接操作内存。这些变量和相关方法没有在上述代码片段中显示,因为它们涉及到更底层的 JVM 和硬件操作。在实际应用中,我们通常不需要直接使用这些底层功能,而是通过 AtomicReference 提供的高级方法来操作引用值。