原子类是怎么使用CAS保证线程安全的?

原子类

原子性意味着“一组操作要么全部操作成功,要么全都失败!不能只操作成功一部分”

java.util.concurrent.atomic下的类全是具有原子性的类。

原子类的作用类似,是为了保证并发情况下线程安全!不过原子类相比于锁,有一定的优势:

  • 粒度更细:原子变量可以把竞争范围缩小到变量级别
  • 效率更高:原子类底层使用CAS,不会阻塞线程

6类原子类纵览

原子类一共可以分为以下6类:

  • Atomic* 基本类型原子类

    • AtomicInteger
    • AtomicLong
    • AtomicBoolean
  • Atomic*Array 数组类型原子类

    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray
  • Atomic*Reference 引用类型原子类

    • AtomicReference
    • AtomicStampedReference
    • AtomicMarkableReference
  • Atomic*FieldUpdater 升级类型原子类

    • AtomicIntegerFieldUpdater
    • AtomicLongFieldUpdater
    • AtomicReferenceFieldUpdater
  • Adder 累加器

    • LongAdder
    • DoubleAdder
  • Accumulator 积累器

    • LongAccumulator
    • DoubleAccumulator

Atomic*基本类型原子类

有三种,以AtomicInteger为典型。

AtomicInteger 类常用方法

  • public final int get()
  • public final int getAndSet(int newValue)
  • public final int getAndIncrement()//自增
  • public final int getAndDecrement()//自减
  • public final int getAndAdd(int delta)//加上预期值
  • boolean compareAndSet(int expect,int update)//如果输入值等于预期值则以原子方式将该值更新为输入值(update)

Atomic*Array数组类型原子类

让数组类中的每一个元素具有原子性。
有三种----整型、长整型、引用型

Atomic*Reference引用类型原子类

该类让一个对象保证原子性,它的能力比基本类型原子类强,因为一个对象可以包含很多属性。


AtomicStampedReference:对AtomicReference的升级,加入时间戳,解决CAS的ABA问题
AtomicMarkableReference:和AtomicReference类似,多了一个绑定的布尔值,用于标记该对象是否已经删除等场景。

Atomic*FieldUpdater 原子更新器

称为原子更新器,有三种---整型、长整型、引用。
加入之前有了一个变量,比如是int,实际它并不具备原子性。但是它已经声明了,此时可以使用AtomicIntegerFieldUpdater把已经升级的变量进行升级,这样这个变量就拥有了CAS操作的能力。

这个对象一开始没有声明为原子类,一方面可能是出于历史原因,另一方面是只有极少数部分下才需要原子操作,原子类型比普通类型更加耗费资源!

public class AtomicIntegerFieldUpdaterDemo implements Runnable{
 
   static Score math;
   static Score computer;
 
   public static AtomicIntegerFieldUpdater<Score> scoreUpdater = AtomicIntegerFieldUpdater
           .newUpdater(Score.class, "score");
 
   @Override
   public void run() {
       for (int i = 0; i < 1000; i++) {
           computer.score++;
           scoreUpdater.getAndIncrement(math);
       }
   }
 
   public static class Score {
       volatile int score;
   }
 
   public static void main(String[] args) throws InterruptedException {
       math =new Score();
       computer =new Score();
       AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo();
       Thread t1 = new Thread(r);
       Thread t2 = new Thread(r);
       t1.start();
       t2.start();
       t1.join();
       t2.join();
       System.out.println("普通变量的结果:"+ computer.score);
       System.out.println("升级后的结果:"+ math.score);
   }
}

两个score类型的属性--math和computer,score类型内部有一个分数。此时用普通类型和升级后的类型进行多线程累加,可以看到最后结过不一样!

Adder 加法器和Accumulator 积累器后续再说

以AtomicInteger为例,分析Java中如何利用CAS实现原子操作

getAndADD

//JDK 1.8实现
public final int getAndAdd(int delta) {    
   return unsafe.getAndAddInt(this, valueOffset, delta);
}

使用unsafe类。

unsafe类

这是CAS的核心类,由于Java无法直接访问底层的操作系统,所以需要通过native方法来实现。此外JVM还留了一个unsafe类,它提供硬件级别的原子操作,可以利用unsafe类直接操作内存。

public class AtomicInteger extends Number implements java.io.Serializable {
   // setup to use Unsafe.compareAndSwapInt for updates
   private static final Unsafe unsafe = Unsafe.getUnsafe();
   private static final long valueOffset;
 
   static {
       try {
           valueOffset = unsafe.objectFieldOffset
               (AtomicInteger.class.getDeclaredField("value"));
       } catch (Exception ex) { throw new Error(ex); }
   }
 
   private volatile int value;
   public final int get() {return value;}
   ...
}

类初始化使,有一个static方法块获取原子类的value的偏移量(内存偏移地址),这个值赋给了valueOffset变量!

value使用volatile修饰

继续查看getAndAddInt

public final int getAndAddInt(Object var1, long var2, int var4) {
   int var5;
   do {
       //这是个native方法,var1是当前类,var2是偏移量,此时var5就是当前时刻下的原子类值
       var5 = this.getIntVolatile(var1, var2);
   } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
       /**while退出的条件,四个参数
          1、var1:当前操作对象
          2、var2:偏移量
          3、var5:期望量
          4、var5+var4:var4是传入的delta,即更新值
          要想更改成功的前提是:当前获取的偏移量(var5)==最初的偏移量(var2),所以需要循环不停的查询var5直到等于var2,才进行更改
          compareAndSwapInt的作用就是:判断如果现在的原子类中的value值和之前获取的var5相同,就更新值,更新失败就再次进入while循环,这个时候var5已经更新,之后继续CAS的操作去尝试更新
       */

   return var5;
}

getAndAddInt通过循环+CAS方式实现原子操作

Last modification:April 20th, 2020 at 01:00 am