《Java并发编程实战》课程笔记(十四)

原子类:无锁工具类的典范

  • 对于简单的原子性问题,还有一种无锁方案。Java SDK 并发包将这种无锁方案封装提炼之后,实现了一系列的原子类。
  • 无锁方案相对互斥锁方案,最大的好处就是性能。
    • 互斥锁方案为了保证互斥性,需要执行加锁、解锁操作,而加锁、解锁操作本身就消耗性能;同时拿不到锁的线程还会进入阻塞状态,进而触发线程切换,线程切换对性能的消耗也很大。
    • 相比之下,无锁方案则完全没有加锁、解锁的性能消耗,同时还能保证互斥性,既解决了问题,又没有带来新的问题,可谓绝佳方案。

无锁方案的实现原理

  • 其实原子类性能高的秘密很简单,硬件支持而已。
    • CPU 为了解决并发问题,提供了 CAS 指令(CAS,全称是 Compare And Swap,即“比较并交换”)。
    • CAS 指令包含 3 个参数:共享变量的内存地址 A、用于比较的值 B 和共享变量的新值 C;
    • 并且只有当内存中地址 A 处的值等于 B 时,才能将内存中地址 A 处的值更新为新值 C。
    • 作为一条 CPU 指令,CAS 指令本身是能够保证原子性的。
  • 使用 CAS 来解决并发问题,一般都会伴随着自旋,而所谓自旋,其实就是循环尝试。

看 Java 如何实现原子化的 count += 1

  • 在 Java 1.8 版本中,getAndIncrement() 方法会转调 unsafe.getAndAddLong() 方法。这里 this 和 valueOffset 两个参数可以唯一确定共享变量的内存地址。
    final long getAndIncrement() {
    	return unsafe.getAndAddLong(this, valueOffset, 1L);
    }
    
    • unsafe.getAndAddLong() 方法首先会在内存中读取共享变量的值,之后循环调用 compareAndSwapLong() 方法来尝试设置共享变量的值,直到成功为止。
    • compareAndSwapLong() 是⼀个 native 方法,只有当内存中共享变量的值等于 expected 时,才会将共享变量的值更新为 x,并且返回 true;否则返回 fasle。
    • compareAndSwapLong 的语义和 CAS 指令的语义的差别仅仅是返回值不同而已。
  • Java 提供的原子类里面 CAS 一般被实现为 compareAndSet(),compareAndSet() 的语义和 CAS 指令的语义的差别仅仅是返回值不同而已,compareAndSet() 里面如果更新成功,则会返回 true,否则返回 false。

原子类概览

《Java并发编程实战》课程笔记(十四)_第1张图片

  • 原子化的基本数据类型
    • 相关实现有 AtomicBoolean、AtomicInteger 和 AtomicLong。
  • 原子化的对象引用类型
    • 相关实现有 AtomicReference、AtomicStampedReference 和 AtomicMarkableReference,利用它们可以实现对象引用的原子化更新。
    • 对象引用的更新需要重点关注 ABA 问题,AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题。
    • 解决 ABA 问题的思路其实很简单,增加一个版本号维度就可以了,每次执行 CAS 操作,附加再更新一个版本号,只要保证版本号是递增的,那么即便 A 变成 B 之后再变回 A,版本号也不会变回来(版本号递增的)。
  • 原子化数组
    • 相关实现有 AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray,利用这些原子类,我们可以原子化地更新数组里面的每一个元素。
    • 这些类提供的方法和原子化的基本数据类型的区别仅仅是:每个方法多了一个数组的索引参数。
  • 原子化对象属性更新器
    • 相关实现有 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和
      AtomicReferenceFieldUpdater,利用它们可以原子化地更新对象的属性,这三个方法都是利用反射机制实现的。
    • 对象属性必须是 volatile 类型的,只有这样才能保证可见性;
    • 如果对象属性不是 volatile 类型的,newUpdater() 方法会抛出 IllegalArgumentException 这个运行时异常。
  • 原子化的累加器
    • DoubleAccumulator、DoubleAdder、LongAccumulator 和 LongAdder,这四个类仅仅用来执行累加操作,相比原子化的基本数据类型,速度更快,但是不支持 compareAndSet() 方法。
    • 如果你仅仅需要累加操作,使⽤原子化的累加器性能会更好。

你可能感兴趣的:(Java,基础,java,笔记,jvm)