无锁的原子类操作使用系统的CAS指令,Java8中引入了LongAdder类,来进一步提高性能。
AtomicInteger的基本实现原理是,在一个死循环内,不断尝试修改目标值,直至修改成功。如果竞争不激烈,则修改成功率就比较高,否则失败率就很高。当大量修改失败时,这些原子操作就会进行多次死循环尝试,导致性能受到影响。
当竞争激烈时可以使用热点分离,将竞争的数据进行分解进而提升性能。虽然CAS操作中没有锁,但减小锁粒度的思想依然可以使用。一种方案是仿造ConcurrentHashMap将热点数据分离,比如将AtomicInteger的内部核心数据value分离成一个数组,每个线程访问时,通过哈希算法映射到其中一个数字进行计数,最终的计数结果为此数组的求和值。LongAdder类正是使用了这种思想来实现的。
在实际操作中LongAdder并不会一开始就用数组进行处理,而是将所有数据都记录在一个称为base的变量中。如果在多线程下修改base没有冲突,则没有必要扩展为cell数组。一旦base修改发生冲突,就会初始化cell数组,使用新的策略。如果cell数组更新后,发现在某个cell上更新依然冲突,则系统会尝试创建新的cell或将cell的数量加倍,以降低冲突的可能性。
increment()方法会将LongAdder自增1,开始时cells=null,数据会向base增加,如果对base的操作冲突,则会设置冲突标记uncontended=true。如果cells不可用或当前线程对应的cell=null,则进入longAccumulate()方法。longAccumulate()的功能是根据需要创建新的cell或对cells扩容,以减少冲突。
以下是LongAdder、原子类和同步锁的性能测试:
LongAdder的另一个优化是避免了伪共享,它使用注解方式实现(@sun.misc.Contended),JVM会自动为Cell解决伪共享问题。我们也可以使用sun.misc.Contended来解决伪共享问题(可以声明在类上),但需要设置JVM参数-XX:-RestrictContended,否则该注释将被忽略。
LongAdder中cell定义:
LongAdder的增强版:LongAccumulator
LongAccumulator同样继承于Striped64,它的内部优化方式和LongAdder一样,都是将一个long型值进行分割,存储在不同变量中,防止多线程竞争。LongAccumulator是LongAdder的扩展,LongAdder只是每次对给定的整数执行一次加法,而LongAccumulator可以实现任意函数操作。
构造方法的第一个参数accumulatorFunction是需要执行的二元函数,第二个参数是初始值。
--参考文献《实战Java高并发程序设计》