0%

无锁(乐观锁)

  • CAS 的全称是 Compare And Swap,是 CPU 并发原语

    • CAS 并发原语体现在 Java 语言中就是 sun.misc.Unsafe 类的各个方法,调用 UnSafe 类中的 CAS 方法,JVM 会实现出 CAS 汇编指令,这是一种完全依赖于硬件的功能,实现了原子操作
    • CAS 是一种系统原语,原语属于操作系统范畴,是由若干条指令组成 ,用于完成某个功能的一个过程,并且原语的执行必须是连续的,执行过程中不允许被中断,所以 CAS 是一条 CPU 的原子指令,不会造成数据不一致的问题,是线程安全的

    底层原理:CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核和多核 CPU 下都能够保证比较交换的原子性

    • 程序在单核处理器上运行时,会省略 lock 前缀,单处理器自身会维护处理器内的顺序一致性,不需要 lock 前缀的内存屏障效果
    • 程序在多核处理器上运行时,会为 cmpxchg 指令加上 lock 前缀。当某个核执行到带 lock 的指令时,CPU 会执行总线锁定或缓存锁定,将修改的变量写入到主存,这个过程不会被线程的调度机制所打断,保证了多个线程对内存操作的原子性

    作用:比较当前工作内存中的值和主物理内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止

无锁(乐观锁)

★CAS

  • CAS 的全称是 Compare And Swap,是 CPU 并发原语

    • CAS 并发原语体现在 Java 语言中就是 sun.misc.Unsafe 类的各个方法,调用 UnSafe 类中的 CAS 方法,JVM 会实现出 CAS 汇编指令,这是一种完全依赖于硬件的功能,实现了原子操作
    • CAS 是一种系统原语,原语属于操作系统范畴,是由若干条指令组成 ,用于完成某个功能的一个过程,并且原语的执行必须是连续的,执行过程中不允许被中断,所以 CAS 是一条 CPU 的原子指令,不会造成数据不一致的问题,是线程安全的

    底层原理:CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核和多核 CPU 下都能够保证比较交换的原子性

    • 程序在单核处理器上运行时,会省略 lock 前缀,单处理器自身会维护处理器内的顺序一致性,不需要 lock 前缀的内存屏障效果
    • 程序在多核处理器上运行时,会为 cmpxchg 指令加上 lock 前缀。当某个核执行到带 lock 的指令时,CPU 会执行总线锁定或缓存锁定,将修改的变量写入到主存,这个过程不会被线程的调度机制所打断,保证了多个线程对内存操作的原子性

    作用:比较当前工作内存中的值和主物理内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止

  • CAS 与 volatile:

    • 获取共享变量时,为了保证该变量的可见性,需要使用 volatile 关键字修饰。它可以用来修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量的值,而必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改,对另一个线程可见。
    • CAS 必须借助 volatile 才能读取到共享变量的最新值来实现 “比较并交换” 的效果
  • CAS 特点:

    • CAS 体现的是无锁并发、无阻塞并发,线程不会陷入阻塞,线程不需要频繁切换状态(上下文切换,系统调用)
    • CAS 是基于乐观锁的思想

    CAS 缺点:

    • 执行的是循环操作,如果比较不成功一直在循环,最差的情况某个线程一直取到的值和预期值都不一样,就会无限循环导致饥饿,使用 CAS 线程数不要超过 CPU 的核心数,采用分段 CAS 和自动迁移机制
    • 只能保证一个共享变量的原子操作
      • 对于一个共享变量执行操作时,可以通过循环 CAS 的方式来保证原子操作
      • 对于多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候只能用锁来保证原子性
  • CAS 与 synchronized 的比较:

    • synchronized 是从悲观的角度出发:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程),因此 synchronized 也称之为悲观锁,ReentrantLock 也是一种悲观锁,性能较差
    • CAS 是从乐观的角度出发:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。如果别人修改过,则获取现在最新的值,如果别人没修改过,直接修改共享数据的值,CAS 这种机制也称之为乐观锁,综合性能较好

Atomic类

原子整数(针对基本数据类型)

  • 原子整数类:AtomicIntegerAtomicBooleanAtomicLong

  • AtomicInteger 为例。

    • 构造方法:

      • public AtomicInteger():初始化一个默认值为 0 的原子型 Integer
      • public AtomicInteger(int initialValue):初始化一个指定值的原子型 Integer
    • 常用 API:

      方法作用
      public final int get()获取 AtomicInteger 的值
      public final int getAndIncrement()以原子方式将当前值加 1,返回的是自增前的值
      public final int incrementAndGet()以原子方式将当前值加 1,返回的是自增后的值
      public final int getAndSet(int value)以原子方式设置为 newValue 的值,返回旧值
      public final int addAndGet(int data)以原子方式将输入的数值与实例中的值相加并返回 实例:AtomicInteger 里的 value

    AtomicInteger 原理:自旋锁 + CAS 算法

    • CAS 算法:有 3 个操作数(内存值 V, 旧的预期值 A,要修改的值 B)
      • 当旧的预期值 A == 内存值 V 此时可以修改,将 V 改为 B
      • 当旧的预期值 A != 内存值 V 此时不能修改,并重新获取现在的最新值,重新获取的动作就是自旋

原子引用(针对引用变量)

  • 原子引用:对 Object 进行原子操作,提供一种读和写都是原子性的对象引用变量。

    和前面的原子整数类似,原子整数是对基本数据类型的修改进行原子操作,原子引用是对引用变量的修改进行原子操作

    • 原子引用类:AtomicReferenceAtomicStampedReferenceAtomicMarkableReference
      • AtomicReference 只看值能不能对的上,只要对的上就能改
      • AtomicStampedReference 除了看值对不对的上,还要看这期间是不是改过好几次了,也就是说虽然值对的上,但有可能值已经改过好几次了,只是最后又改回到原来的值,改动的次数用版本号 stamp 来记录
      • AtomicMarkableReference 不关心改动了几次,只关心是否更改过,是否更改过用 mark 来记录
  • AtomicReference 为例。

    • 构造方法:AtomicReference<T> atomicReference = new AtomicReference<T>()
    • 常用 API:
      • public final boolean compareAndSet(V expectedValue, V newValue):CAS 操作
      • public final void set(V newValue):将值设置为 newValue
      • public final V get():返回当前值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class AtomicReferenceDemo {
    public static void main(String[] args) {
    Student s1 = new Student(33, "AA");

    // 创建原子引用包装类
    AtomicReference<Student> atomicReference = new AtomicReference<>();
    // 设置主内存共享变量为s1
    atomicReference.set(s1);

    // 比较并交换,如果现在主物理内存的值为 AA,那么交换成 BB
    while (true) {
    Student s2 = new Student(44, "BB");
    if (atomicReference.compareAndSet(s1, s2)) {
    break;
    }
    }
    System.out.println(atomicReference.get());
    }
    }

    class Student {
    private int id;
    private String name;
    //...
    }

原子数组(针对数组数据)

  • 原子数组类:AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

原子更新器(针对对象字段)

  • 原子更新器类:AtomicReferenceFieldUpdaterAtomicIntegerFieldUpdaterAtomicLongFieldUpdater
    • 利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常 IllegalArgumentException: Must be volatile type

原子累加器(针对基本数据类型的累加操作)

  • 原子累加器类:LongAdderDoubleAdderLongAccumulatorDoubleAccumulator

  • LongAdder 和 LongAccumulator 区别:

    • 相同点:

      • LongAdder 与 LongAccumulator 类都是使用非阻塞算法 CAS 实现的

      • LongAdder 类是 LongAccumulator 类的一个特例,只是 LongAccumulator 提供了更强大的功能,可以自定义累加规则,当accumulatorFunction 为 null 时就等价于 LongAddr

    • 不同点:

      • 调用 casBase 时,LongAccumulator 使用 function.applyAsLong(b = base, x) 来计算,LongAdder 使用 casBase(b = base, b + x)

      • LongAccumulator 类功能更加强大,构造方法参数中

        • accumulatorFunction 是一个双目运算器接口,可以指定累加规则,比如累加或者相乘,其根据输入的两个参数返回一个计算值,LongAdder 内置累加规则
        • identity 则是 LongAccumulator 累加器的初始值,LongAccumulator 可以为累加器提供非 0 的初始值,而 LongAdder 只能提供默认的 0
LongAdder
  • LongAdder 是 Java8 提供的类,跟 AtomicLong 有相同的效果,但对 CAS 机制进行了优化,尝试使用分段 CAS 机制以及自动分段迁移的方式来大幅度提升多线程高并发执行 CAS 操作的性能。

    • CAS 底层实现是在一个循环中不断地尝试修改目标值,直到修改成功。如果竞争不激烈修改成功率很高,否则失败率很高,失败后这些重复的原子性操作会耗费性能(导致大量线程空循环,自旋转
    • LongAdder 将 AtomicLong 的单点的更新压力分担到各个节点,空间换时间,在低并发的时候直接更新,可以保障和 AtomicLong 的性能基本一致,而在高并发的时候通过分散减少竞争,提高了性能

    分段 CAS 机制

    • 在发生竞争时,创建 Cell 数组用于将不同线程的操作离散(通过 hash 等算法映射)到不同的节点上
    • 设置多个累加单元(会根据需要扩容,最大为 CPU 核数),Therad-0 累加 Cell[0],而 Thread-1 累加 Cell[1] 等,最后将结果汇总
    • 在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能

    自动分段迁移机制:某个 Cell 的 value 执行 CAS 失败,就会自动寻找另一个 Cell 分段内的 value 值进行 CAS 操作

unsafe

  • Unsafe 是 CAS 的核心类,由于 Java 无法直接访问底层系统,需要通过本地(Native)方法来访问
    • Unsafe 类存在 sun.misc 包,其中所有方法都是 native 修饰的,都是直接调用操作系统底层资源执行相应的任务,基于该类可以直接操作特定的内存数据,其内部方法操作类似 C 的指针
---------------The End---------------