介绍
Java 提供了 AtomicInteger/AtomicLong 在并发编程里经常用到,它们封装了对 int 和 long 的原子操作。
Java 还提供了 AtomicReference,用于对象引用做原子性的管理,比如 get、set、CAS。
一般情况下 AtomicInteger、AtomicLong 的性能随着线程的增多而急速下降,高并发下甚至不如加锁的版本。因为线程多时竞争太激烈 CPU 都浪费在自旋上面了。
但即使如此,AtomicInteger 和 AtomicLong 的 incrementAndGet 依旧能维持几千万的ops,一般也够用了。
我本以为 AtomicReference 也有着类似的性能,结果发现在 16C32G 机器上 16 个线程并发执行 AtomicReference 的 compareAndSet() 操作只有 200多万的ops. 调用 CAS 时一般会搭配自旋,此时 16 个核全部打满也才 200 多万ops。当时我认为 AtomicReference 和 AtomicLong 的 CAS 操作代价应该相当。
各大网站搜了一下没有讲 AtomicReference 性能的。不过我从几个侧面角度判断 AtomicReference 在高并发下的性能确实明显不如 AtomicInteger、AtomicLong。
- AtomicReference.compareAndSet 操作8字节的对象指针的代价看似与 AtomicLong.compareAndSet 相同, 但这个过程中 JVM 肯定少不了更新各种内部信息, 以便正确的 GC. 所以它的 compareAndSet 肯定不纯粹只是 CAS 8字节的代价.
- 至于具体更新了哪些信息,我目前也不清楚,但感觉跟 GC 有关系
但是,什么场景下会需要这么高并发调用 AtomicReference.compareAndSet 呢?
大概是类似下面的场景
https://github.com/sofastack/sofa-tracer/blob/master/tracer-core/src/main/java/com/alipay/common/tracer/core/reporter/stat/model/StatValues.java#L58
我们要对一组计数器求sum, 这组计数器用 long[] 表示, 我们必须保证这组计数器计数时的原子性, 所以才用的 AtomicReference<long[]>, 否则我们就去用 AtomicLong[] 了.