介紹
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[] 了.