JDK Concurrent包源码阅读2——Atomic类

Posted by 皮皮潘 on 11-26,2021

Atomic类的核心就是自旋 + CAS

AtomicInteger的getAndIncrement方法如下:

for (;;) {
    int current = get();
    int next = current + 1;
    if (compareAndSet(current, next)) return current
}

通过CAS的原子操作,保证一次只能有一个线程可以修改到AtomicInteger,同时又由于没有陷入synchronized从而没有上锁和解锁的开销,因此性能会好非常多,总得来说Atomic类是乐观锁,synchronized是悲观锁

compareAndSet方法底层是调用了Unsafe.compareAndSwapXXX(this, valueOffset, expect, update)方法,其中valueOffset代表了对应字段在对应的类中的内存偏移量(对应变量在内存中的位置),该offset可以通过Unsafe.objectFieldOffset(Field)方法再结合反射计算得到,而CAS方法的具体实现主要通过硬件本身对于一定大小的内存支持原子性的比较并置换指令来实现的

另外由于Atmoic相较于普通类型会多一个8字节的对象头,因此在对于会产生大量对象的情况下,会使用static的AtomicXXXFieldUpdater去替代Atomic,同时AtomicXXXFieldUpdater也可以对于一个已经有的类,在不修改其源代码的情况下,为它对应的成员变量加上原子操作,AtomicXXXFieldUpdater不能直接构造其对象,而是需要通过静态方法——AtomicXXXFieldUpdater(Class<> tclass, String fieldName)实现,其底层会通过objectFieldOffset计算出field对应的类内偏移量,最后在使用时传入tclass对应的实例即可

从JDK8开始,针对Long类型的原子操作,Java提供了LongAdder,它主要针对多线程对于一个Long类型变量并发加的场景,它的核心是用空间换时间,并且牺牲一定程度上的一致性,其具体实现就是把一个变量拆成多份变为多个变量,把一个Long拆分成一个base变量外加多个Cell,每个Cell包装一个Long类型的变量,当多个线程并发加时如果并发度低就直接家在base上,反之则给每个线程分配不同的Cell然后加在不同的Cell上,最后在get对应的Long的时候再把所有的Cell加到base上面即可,整体有些类似于ConcurrentHashMap的分段锁的味道。另外在get的时候并没有对Cell加锁,也就是说它是最终一致性而不是强一致性。另外为了避免伪共享问题,在每个Cell上添加了@Contended自动填充满64个Byte