理解java.util.concurrent包(一)

Java 5.0开始 提供了 java.util.concurrent 包,在此包中增加了在并发编程中很常用的实用工具类。
我们先看java.util.concurrent.atomic中的类:
理解java.util.concurrent包(一)
以上的类均是使用了CAS算法来保证数据的原子性,所谓CAS即Compare and Set。CAS是一种无锁的非阻塞算法的实现,它的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。在多线程环境下,当多个线程同时执行这些类的实例所包含的方法时,具有排他性,抢占到的线程不会被打断,而等待的线程相当于自旋锁,等到抢占线程执行该方法结束之后,由jvm从的能带队列中选择一个线程进入,而这些都是借助硬件相关指令完成。
OK,下边我们分别来看:

1.AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference

以上类的变量都是通过volatile修饰来保证内存可见性,AtomicBoolean是对布尔值的原子操作,AtomicInteger是对整型的原子操作,AtomicLong是对长整型的原子操作,而AtomicReference是对对象的原子操作。

2、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

以上类提供了可以原子读取和写入的底层数组的操作,并且还包含高级原子操作。

3.AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

以上类是基于反射的原子更新字段的值,通俗一点就是通过反射来更新类中的变量。但是更新的字段必须是volatile类型的,同时不能被static和final修饰对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型需要使用AtomicReferenceFieldUpdater,字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

4.AtomicMarkableReference、AtomicStampedReference

这两个类都是解决ABA问题,那么什么是ABA问题呢,假如我们使用AtomicInteger,A线程用将变量其变量m=8修改为9,然后再修改为8,这时B线程读取到m仍然等于8,如果我们只关心结果不关心过程,那么就是无所谓的ABA问题,但是如果我们需要关心过程,那么这时候AtomicMarkableReference和AtomicStampedReference就发挥了作用。AtomicStampedReference利用版本戳的形式记录了每次改变以后的版本号,使用pair的int stamp作为计数器使用,AtomicMarkableReference的pair使用的是boolean mark。 也就是说AtomicStampedReference关心的是修改过几次,而AtomicMarkableReference关心的是是不是被修改过。

5.LongAdder、LongAccumulator,DoubleAccumulator、DoubleAdder

LongAdder是jdk1.8提供的累加器,基于Striped64实现。常用于状态采集、统计等场景。AtomicLong也可以用于这种场景,但在线程竞争激烈的情况下,LongAdder要比AtomicLong拥有更高的吞吐量,但会耗费更多的内存空间。LongAdder的解决方法:减少并发,将单一value的更新压力分担到多个value中去,降低单个value的 “热度”,分段更新,我需要总数时,把cells 中的value都累加一下。LongAccumulator和LongAdder类似,也基于Striped64实现。LongAdder相当于是LongAccumulator的一种特例。
DoubleAccumulator及DoubleAdder同理。