Compare And Swap

背景

Java 在 1.4 版本后,提供了 JUC 下面的很多并发工具类,我们常使用的如下:

  1. AtomicInteger
  2. AtomicLong
  3. AtmoicReference

那么在使用这些原子工具类来确保并发安全的背后,究竟是什么操作,代替了原有的 Sync 重量锁操作呢?CAS Java Util Current 背后的男人。

CAS 概念

Cas 是 Compare And Swap 的简称,他的出现主要是解决了并发操作时,使用 Sync 锁而产生的效率问题,同时他的实现也是乐观锁的一种方式。

L1 缓存概念

L1 缓存是为了提高线程每次直接从主内存读写变量的速度而开发的逻辑缓存单元,各线程 L1 中的数据是独享的。有些处理器还提供了 L2 工作缓存

并发问题

在没有 CAS 出现之前,我们在并发操作一个共享变量的时候,步骤如下:

假设目前有两个线程(T1、T2),同时对内存中的共享变量 x 其值为 0 变量做 +1 操作

  1. 线程 T1 从主内存读取变量 x 值,同时将 x 加入到 L1 缓存
  2. 线程 T2 从主内存读取变量 x 值,同时将 x 加入到 L1 缓存
  3. 线程 T1 对 x 做 +1 操作,此时 x 值为 1,并将其写入到 L1 缓存中
  4. 线程 T2 对 x 做 +1 操作,此时 x 值为 1,并将其写入到 L1 缓存中
  5. 线程 T1 将 L1 缓存中的 x 写入到主内存(此时主内存的 x 值为 1)
  6. 线程 T2 将 L1 缓存中的 x 写入到主内存(此时主内存的 x 值为 1)

经过如上步骤我们发现,最终主内存中的 x 并不符合我们预期对值 2,而这就是并发安全问题。
解决上述问题,以往我们会对操作 x 的方法添加 Sync 关键字,使该方法加锁来解决此类问题。
但是锁的消耗是巨大的,为了这么小的一个改变,而对方法加锁,是不值得的。为此 CAS 诞生。

CAS 解决方式

我们来看一下 CAS 如何解决上述问题:

假设目前有两个线程(T1、T2),同时对内存中的共享变量 x 其值为 0 变量做 +1 操作

  1. 线程 T1 从主内存读取变量 x 值,同时将 x 加入到 L1 缓存
  2. 线程 T2 从主内存读取变量 x 值,同时将 x 加入到 L1 缓存
  3. 线程 T1 对 x 做 +1 操作,此时 x 值为 1,并将其写入到 L1 缓存中
  4. 线程 T2 对 x 做 +1 操作,此时 x 值为 1,并将其写入到 L1 缓存中
  5. 线程 T1 将 L1 缓存中的 x 写入到主内存,在写入之前会再次从主内存中拿到 x 的值,并与开始获取到的 x 值做对比,
    发现相等,然后写入 x 值到主内存(此时主内存的 x 值为 1)
  6. 线程 T2 将 L1 缓存中的 x 写入到主内存,在写入之前会再次从主内存中拿到 x 的值,并与开始获取到的 x 值做对比,
    发现不等,此时 T2 线程不会将 x 值写入到主内存,而是返回到第一步操作,读取主内存中 x 的值,并对其做 +1 操作,
    然后再次判断主内存中当前 x 与开始获取到的 x 做对比(此时 x 为 1),直到相等后,然后写入 x 值到主内存(此时主内存的 x 值为 2)

以上便是 CAS 操作到步骤,在与之前线程直接将 x 值协会主内存中不同,CAS 此时会做一个对比,将开始获取到到 x 与当前
主内存的 x 做对比,如果一致则写入主内存,如果不一致则回到获取 x 值的步骤重新执行,直至 x 对比一致后写入主内存

CAS 流程图如下
ABA 问题(对象引用)
乐观锁


Java     

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!