原文: https://howtodoinjava.com/java/multi-threading/compare-and-swap-cas-algorithm/

Java 5 中最好的补充之一是AtomicIntegerAtomicLong等类中支持的原子操作。这些类可帮助您将复杂的(不必要的)多线程用于一些基本操作的代码,例如递增或递减在多个线程之间共享的值。 这些类在内部依赖于名为 CAS(比较和交换)的算法。 在本文中,我将详细讨论这个概念。

1. 乐观锁和悲观锁

传统的锁定机制,例如在 Java 中使用同步的关键字的被称为锁定或多线程的悲观技术。 它要求您首先保证在特定操作之间没有其他线程会干扰(即锁定对象),然后仅允许您访问任何实例/方法。

这就像说“请先关上门; 否则,其他骗子会进来重新整理您的东西。”

尽管上述方法是安全的并且确实有效,但是在性能上对您的应用程序造成了重大损失。 原因很简单,等待线程无法做任何事情,除非它们也有机会执行受保护的操作。

还有一种方法在性能上更有效,并且本质上是乐观的。 通过这种方法,您可以进行更新,希望可以完成更新而不会受到干扰。 此方法依靠冲突检测来确定在更新期间是否存在来自其他方的干扰,在这种情况下,操作将失败并且可以重试(或不重试)。

乐观的方法就像老话所说:“获得宽容比得到许可更容易”,这里的“轻松”意味着“更有效率”。

比较并交换是这种乐观方法的一个很好的例子,我们将在下面讨论。

2. 比较和交换算法

该算法将存储位置的内容与给定值进行比较,并且只有它们相同时,才会将该存储位置的内容修改为给定的新值。 这是作为单个原子操作完成的。 原子性保证了根据最新信息计算新值; 如果与此同时值已由另一个线程更新,则写入将失败。 操作的结果必须表明它是否执行了替换; 这可以通过简单的布尔响应(此变量通常称为“比较设置”)来完成,也可以通过返回从内存位置读取的值(而不是写入该值)来完成。

CAS 操作有 3 个参数:

  1. 必须替换值的存储位置V
  2. 线程上次读取的旧值A
  3. 应该写在V上的新值B

CAS 说:“我认为V应该具有值A; 如果可以,则将B放在此处,否则不要更改它,但要告诉我我错了。” CAS 是一种乐观技术,它希望成功进行更新,并且自从上次检查变量以来,如果另一个线程更新了该变量,则可以检测到失败。

3. Java 比较和交换示例

让我们通过一个例子来了解整个过程。 假设V是存储值“10”的存储位置。 有多个线程想要递增此值并将递增的值用于其他操作,这是一种非常实际的方案。 让我们分步介绍整个 CAS 操作:

1)线程 1 和 2 想要增加它,它们都读取值并将其增加到 11。

V = 10, A = 0, B = 0

2)现在线程 1 首先出现,并将V与最后读取的值进行比较:

V = 10, A = 10, B = 11

  1. if A = V
  2. V = B
  3. else
  4. operation failed
  5. return V

显然,V的值将被覆盖为 11,即操作成功。

3)线程 2 到来并尝试与线程 1 相同的操作

V = 11, A = 10, B = 11

  1. if A = V
  2. V = B
  3. else
  4. operation failed
  5. return V

4)在这种情况下,V不等于A,因此不替换值,并且返回V的当前值即 11。 现在线程 2,再次使用以下值重试此操作:

V = 11, A = 11, B = 12

而这一次,条件得到满足,线程 2 返回增量值 12。

总而言之,当多个线程尝试使用 CAS 同时更新同一变量时,一个将获胜并更新该变量的值,而其余则将丢失。 但是失败者并不会因为线程中断而受到惩罚。 他们可以自由地重试该操作,或者什么也不做。

这就是与 Java 支持的原子操作有关的这个简单但重要的概念的全部。

祝您学习愉快!