天天看点

Atomic*系列原子类相关知识

文章目录

      • Atomic*特点
      • Atomic*系列
      • Atomic*缺点
      • 参考

Atomic*特点

  1. Atomic* 按锁的类型来区分应该是属于乐观锁,就是多个线程在读取使用变量的时候是互不影响,非阻塞式的。
  2. sun.misc.Unsafe里面包含了大量的C代码和多种直接操作内存和原子能力的方法,所以被标记为不安全的,Atomic*系列主要就是使用了这个类的能力,来实现了非阻塞时的线程安全。Unsafe是一个非对外的类,内部是采用了单例模式来实现的,如果需要使用的话,可以通过反射的方式获取Unsafe内部的theUnsafe成员变量,从而获取Unsafe的实例。
    • Unsafe类中包含了大量compareAndSwap* 的方法,支持int、long、object、array等类型,参数都是类似的。
    • obj指的是要修改的变量所在的类地址,也就是起始内存地址,数组则对应第一个元素的内存地址;
    • argAddrOffset指的是基于obj的地址,需要偏移的地址,即待修改的变量地址=obj的起始地址+argAddrOffset,数组则直接对应下表,UnSafe这个类中有大量的方法可以获取某个变量的偏移量地址,如成员变量使用objectFieldOffset方法获取变量偏移量,静态变量使用staticFieldOffset方法获取变量便宜量;
    • expectValue指的是期望的值,即上一次读取到该线程工作内存中的值,如果两个值一致,则说明变量没有被修改,即可以直接将updateValue的值更新到主内存,否则更新失败,需要注意的是,这个比较和更新的动作是原子的,都JVM底层定义的原子操作来保证其原子性;
    • updateValue指的是待更新到主内存中的值。
    • 返回值为true则说明更新成功,返回false则说明更新识别,有可能是其他的线程更细了这个值
    U.getAnd*(obj,argAddrOffset,value)
               
    • 获取并做某些处理,可以是增加也可以是替换,如getAndAddInt()、getAndSetInt()等,返回的值都是修改之前的值,并且这些操作都是源自的操作,有JVM底层来保证。
  3. UnSafe类只提供了先获取再更新的方法,所以Atomic中提供的更新再获取的方法都是上层基于getAnd方法自定义逻辑来做的。
  4. Atomic*通过UnSafe来保证原子性,通过使用volatile来修饰value变量从而保证可见性,从而达到了我们的目的。

Atomic*系列

  • AtomicInteger
  • AtomicBoolean【底层是通过AtomicInteger来实现的,通过0-false,1-true来表示】
  • AtomicLong
  • AtomicReference

都是用原子的方式来更新指定类型的值

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

可以直接操作数组的某个元素的值,保证更改元素的原子性,另外所有的array都是经过一次clone后才存储到Atomic*中的

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

可以修改某些类的成员变量,前提是这个类的变量是非静态的,且是非final的,可访问的

  • AtomicMarkableReference
  • AtomicStampedReference

添加了版本号的原子类,可以用来解决ABA问题,Markable只支持true、false两种状态,stamped支持多种状态

  • DoubleAccumulator【与LongAccumulator类型,只是类型变成了Double】
  • DoubleAdder【与LongAdder类型,只是类型变成了Double】
  • LongAccumulator【比LongAdder强大,支持只定义base的初始值,以及自定义累计方式,LongAdder相当于是base为0和累计方式固定为相加的LongAccumulator】
  • LongAdder【相当于是LongAccumulator的一个特例,base为0的特例】

jdk1.8增加的新特性,主要是增加了吞吐量,主要是通过在cas冲突时采用变量数组Cell[]的方式来将该线程待更新的值先更新到数组的其他位置,有点类似ConcurrentHashMap中的分段锁的概念,他们都有一个通病:由于在内部没有加锁,所以在多线程中调用sum和reset等方法,会出现不精确的问题,但是在单线程中调用是正确的,该类型的变量可以用在统计类似点击量和浏览量等并发高,但不要求精确的场景中,不能替代Atomic*系列

Atomic*缺点

  • 当线程之间存在激烈的竞争时,容易出现某个线程长时间无法插入数据,从而导致多次循环,浪费CPU资源的情况。
  • 同时采用普通的CAS来保证数据的安全,还容易出现ABA的问题,即某个数据从一个初始状态,经过多个状态变化后又回到初始状态,却被当做该数据是没有被更改过,这种情况在某些业务场景中是有问题的,这种问题可以通过采用带版本号的修改的方式来解决,即给每一次修改都添加一个版本。

参考

Java JUC之Atomic系列12大类实例讲解和原理分解

线程安全性详解(原子性、可见性、有序性)