并发编程-JUC-原子类

JUC 整体概览

并发编程-JUC-原子类_第1张图片

原子类

  • 基本类型-使用原子的方式更新基本类型
    • AtomicInteger:整形原子类
    • AtomicLong:长整型原子类
    • AtomicBoolean :布尔型原子类
  • 引用类型
    • AtomicReference:引用类型原子类
    • AtomicStampedReference:原子更新引用类型里的字段原子类
    • AtomicMarkableReference :原子更新带有标记位的引用类型
  • 数组类型-使用原子的方式更新数组里的某个元素
    • AtomicIntegerArray:整形数组原子类
    • AtomicLongArray:长整形数组原子类
    • AtomicReferenceArray :引用类型数组原子类
  • 对象的属性修改类型
    • AtomicIntegerFieldUpdater:原子更新整形字段的更新器
    • AtomicLongFieldUpdater:原子更新长整形字段的更新器
    • AtomicReferenceFieldUpdater :原子更新引用类形字段的更新器
  • JDK1.8新增类
    • DoubleAdder:双浮点型原子类
    • LongAdder:长整型原子类
    • DoubleAccumulator:类似DoubleAdder,但要更加灵活(要传入一个函数式接口)
    • LongAccumulator:类似LongAdder,但要更加灵活(要传入一个函数式接口)

原子类实现原理CAS

CAS其实就是执行一个函数:CAS(V,E,N)直到成功为止。

CAS操作涉及到三个操作数:
V:要读写的内存地址
E:进行比较的值 (预期值)这个期望值是每次自旋的时候从V中读取出来,然后更新的时候再比较这个值和V中的值是否相等如果相等表示没有其它线程来修改,否则被修改过,继续自旋。直到成功为止。
N:拟写入的新值

// 以AtomicInteger为例
public final int getAndAddInt(Object var1, long var2, int var4) {
       int var5;
       do {
           var5 = this.getIntVolatile(var1, var2);
       } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

       return var5;
   }

compareAndSwapInt是一个native方法:

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe,
jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);//将Java对象解析成JVM的oop(普通对象指针)
//根据对象p内存地址和内存地址偏移量计算拟修改对象属性的地址
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
//基于cas比较并替换,x表示拟更新的值, addr表示要操作的内存地址, e表示预期值 这是一个原子的方法
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

CAS存在的缺陷

主要存在三个缺陷:循环时间太长、只能保证一个共享变量原子操作、ABA问题。

  • 循环时间太长:如果CAS一直不成功呢?如果自旋CAS长时间地不成功,则会给CPU带来非常大的开销。
    • 原子类AtomicInteger#getAndIncrement()的方法
  • 只能保证一个共享变量原子操作:看了CAS的实现就知道这只能针对一个共享变量,如果是多个共享变量就只能使用锁了。
  • ABA问题:CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的ABA问题。

ABA问题演示

public static void main(String[] args) {
      AtomicInteger at = new AtomicInteger(100);
      AtomicStampedReference<Integer> st = new AtomicStampedReference(100, 1);
      new Thread(() -> {
          // 值增1
          int i = at.incrementAndGet();
          System.out.println("AtomicInteger after modify : " + i);
          // 撤回修改 模拟 A -> B
          int j = at.decrementAndGet();
          System.out.println("AtomicInteger after modify redo : " + j);
          // 值增1 版本号 + 1 为 2
          st.compareAndSet(st.getReference(), st.getReference() + 1, st.getStamp(), st.getStamp() + 1);
          System.out.println("AtomicStampedReference after modify : " + i + " version is : " + st.getStamp());
          // 撤回修改 模拟 A -> B 只不过这个时候我们携带了一个版本号 版本号 + 1 为 3
          st.compareAndSet(st.getReference(), st.getReference() - 1, st.getStamp(), st.getStamp() + 1);
          System.out.println("AtomicStampedReference after modify  redo : " + j + " version is : " + st.getStamp());
      }).start();
      new Thread(() -> {
          // 更新为102可以成功 这个数据其实是被改过一次
          at.compareAndSet(at.get(), at.get() + 2);
          System.out.println("AtomicInteger after modify in other Thread: " + at.get());
          // 更新为102不可以成功 获取的还是100 因为我们预期的版本号是1 也就是第一次的100 所以修改不成功
          st.compareAndSet(st.getReference(), st.getReference() + 2, 1, st.getStamp() + 1);
          // 没有修改成功所以版本号还是 3
          System.out.println("AtomicStampedReference after modify : " + st.getReference() + " version is : " + st.getStamp());
      }).start();
  }

并发编程-JUC-原子类_第2张图片

你可能感兴趣的:(Java,多线程与并发,Java,CAS,ABA,原子类)