首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java AtomicInteger 整数原子操作类源码解读

看源码,如果英文好一点,理解起来会更快一些。英文单词词汇量大的好处就是一下子能够看懂变量或方法的用途。词汇量不够用,只能一边看源码,一边查看单词的含义。

CAS(Compare-And-Swap)比较并交换,比较的结果相等才交换。

下面我们深入解读 AtomicInteger 的源码结构和其实现细节。

1. 类的基本结构

AtomicInteger 内部通过一个 volatile 修饰的 int 类型变量 value 来存储数值,这样可以保证可见性。同时,它基于 Unsafe 类中的底层操作来实现原子性。

这里的 valueOffset 是 value 在内存中的偏移量,通过 Unsafe 提供的 objectFieldOffset 方法获取。

Unsafe 是一个 Java 中提供的可以直接操作内存的类,它的 compareAndSwapInt 方法被用于实现 CAS 操作。Unsafe看着类名就是不安全的。

2. get 和 set 方法

get():获取当前 value 的值,这个操作是直接返回,不涉及 CAS。

set(int newValue):将当前 value 设置为 newValue,但不具备原子性,因此在多线程环境下不一定可靠。

public final int get() {

return value;

}

public final void set(int newValue) {

value = newValue;

}

3. compareAndSet 方法

compareAndSet 是 AtomicInteger 的核心方法,依赖 Unsafe.compareAndSwapInt 来实现。它的作用是比较当前值和预期值是否相等,如果相等就将其更新为新值,否则什么也不做。

public final boolean compareAndSet(int expect, int update) {

return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

}

这里,compareAndSwapInt 会比较 value 是否等于 expect,如果是,则将其更新为 update,并返回 true。如果不相等,直接返回 false。这是一个无锁的原子操作,因此不会阻塞其他线程。

4. getAndIncrement 和 incrementAndGet 方法

getAndIncrement():先获取当前值,然后进行加 1。

incrementAndGet():先加 1,然后返回新值。

这两个方法的实现也依赖 compareAndSet,通过一个自旋锁的方式实现“乐观锁”效果。

public final int getAndIncrement() {

return getAndAdd(1);

}

public final int incrementAndGet() {

return addAndGet(1);

}

getAndAdd(int delta):在 delta 值上进行加法操作并返回旧值。

addAndGet(int delta):返回加法操作后的新值。

这些操作利用了 CAS 的自旋机制,即在更新失败时会不断尝试,直到成功为止。这种方式避免了锁机制的开销,提高了性能。

5. getAndSet(int newValue)

getAndSet 方法用于获取当前值并设置为新值,其核心逻辑依然依赖 CAS。这个方法的操作是“先获取再设置”,返回旧值并设置成新值。

public final int getAndSet(int newValue) {

for (;;) {

int current = get();

if (compareAndSet(current, newValue))

return current;

}

}

在 for 循环中,compareAndSet 会不断尝试在 value 等于 current 时将其更新为 newValue,直到成功为止。这种实现避免了锁的使用,确保了多线程环境下的线程安全。

在并发情况下,AtomicInteger 通过 自旋 来实现更新重试。这里的 for 循环是一个自旋锁的典型实现,直到 compareAndSet 成功为止。

6. getAndAdd(int delta)

getAndAdd 方法允许对 AtomicInteger 进行增量或减量操作,并返回旧值。它的实现与 getAndIncrement 类似,通过传入的 delta 值来决定加减数。

public final int getAndAdd(int delta) {

for (;;) {

int current = get();

int next = current + delta;

if (compareAndSet(current, next))

return current;

}

}

getAndAdd(1) 与 getAndIncrement() 等效。

getAndAdd(-1) 等效于 getAndDecrement()。

这个方法在并发情况下会不断尝试,直到 compareAndSet 成功。

7. addAndGet(int delta)

addAndGet 与 getAndAdd 的实现基本相同,区别在于它返回的是新值(加 delta 后的值),而不是旧值。

public final int addAndGet(int delta) {

for (;;) {

int current = get();

int next = current + delta;

if (compareAndSet(current, next))

return next;

}

}

这两个方法都采用自旋锁的方式,以确保更新的原子性。

8. lazySet(int newValue)

lazySet 主要用于延迟设置变量值,在一些高并发环境下可以优化性能。lazySet 不会立刻刷新到主存,而是允许 JVM 延迟到适当时机再同步到主存,确保最终一致性。它适合在不要求强一致性的场景中使用。

public final void lazySet(int newValue) {

unsafe.putOrderedInt(this, valueOffset, newValue);

}

putOrderedInt 是一种轻量级的写入操作,相对于 volatile 的直接写入,它更节省性能,但保证最终能被其他线程读取到最新值。

9. weakCompareAndSet(int expect, int update)

weakCompareAndSet 与 compareAndSet 的主要区别在于它的 CAS 操作不保证强一致性。在某些平台上,weakCompareAndSet 可以允许失败而不重试。这种设计是为了提高在某些情况下的性能,适合对一致性要求不严格的场景。

public final boolean weakCompareAndSet(int expect, int update) {

return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

}

尽管其内部实现与 compareAndSet 看似相同,但 JVM 可以对 weakCompareAndSet 进行优化,在高性能需求下减少 CAS 操作的重试次数。

10. accumulateAndGet 和 getAndAccumulate

Java 8 引入了函数式编程接口,使得 AtomicInteger 更灵活。这两个方法允许用户通过指定的操作函数来更新 AtomicInteger 的值:

accumulateAndGet:使用指定函数进行累加,返回新值。

getAndAccumulate:先获取旧值,然后使用指定函数进行累加,返回旧值。

例如,accumulateAndGet 实现如下:

public final int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) {

int prev, next;

do {

prev = get();

next = accumulatorFunction.applyAsInt(prev, x);

} while (!compareAndSet(prev, next));

return next;

}

这些方法为 AtomicInteger 提供了更灵活的操作方式,用户可以通过自定义累加函数来实现复杂的业务逻辑。

11. updateAndGet 和 getAndUpdate

这两个方法类似于 accumulateAndGet 和 getAndAccumulate,但它们使用的是 一元操作(IntUnaryOperator),适合对 AtomicInteger 进行单值更新。例如:

updateAndGet:使用指定操作更新值,返回新值。

getAndUpdate:先获取当前值,然后使用指定操作更新值,返回旧值。

updateAndGet 示例:

public final int updateAndGet(IntUnaryOperator updateFunction) {

int prev, next;

do {

prev = get();

next = updateFunction.applyAsInt(prev);

} while (!compareAndSet(prev, next));

return next;

}

12. getPlain 和 setPlain

在某些场景下,JDK 9 引入了更细粒度的内存访问控制方法。getPlain 和 setPlain 允许使用非 volatile 的方式获取和设置 AtomicInteger 的值。这些方法提供了更轻量级的内存访问操作,在需要优化性能、且对即时一致性要求不高的场景下适合使用。

public final int getPlain() {

return U.getInt(this, VALUE);

}

public final void setPlain(int newValue) {

U.putInt(this, VALUE, newValue);

}

这些方法允许在没有 volatile 保证的情况下访问值,从而在某些极端性能场景下优化内存操作的开销。

13. getOpaque 和 setOpaque

getOpaque 和 setOpaque 也是 JDK 9 引入的方法,它们在访问和设置变量时,采用了更灵活的内存屏障控制。在某些硬件架构下,它们可能会比 getPlain 和 setPlain 更有效率。

public final int getOpaque() {

return U.getIntOpaque(this, VALUE);

}

public final void setOpaque(int newValue) {

U.putIntOpaque(this, VALUE, newValue);

}

Opaque 模式意味着该操作的结果是不可见的,适合在不要求立即更新或读取一致性的场景下使用。

14. getAcquire 和 setRelease

在并发编程中,我们有时希望只在读取操作前进行同步,或者只在写入操作后进行同步。这就是 getAcquire 和 setRelease 的用途。

getAcquire:保证在获取值之前,之前的所有写入操作都已完成。

setRelease:保证在设置值之后,随后的所有读取操作都能看到该更新。

public final int getAcquire() {

return U.getIntAcquire(this, VALUE);

}

public final void setRelease(int newValue) {

U.putIntRelease(this, VALUE, newValue);

}

这些方法提供了更加灵活的同步控制,可以适应一些特定的并发场景,例如读多写少的情况。

最后总结

通过源码的阅读,我们可以对什么是自旋、CAS机制有个清晰的理解。

AtomicInteger 通过 CAS 机制、Unsafe 类、volatile 关键字等实现了原子操作,避免了使用同步锁,因而在高并发环境中性能优越。AtomicInteger 尤其适用于计数器或计数累加的场景,但由于自旋的存在,过高的并发度也可能带来性能问题。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OtG84Prqs31oxomDiFz5TDQQ0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券