在并发编程中我们都知道i操作是非线程安全的这是因为i操作不是原子操作。如何保证原子性呢常用的方法就是加锁。在Java语言中可以使用Synchronized和CAS实现加锁效果。Synchronized是悲观锁线程开始执行第一步就是获取锁一旦获得锁其他的线程进入后就会阻塞等待锁。如果不好理解举个生活中的例子一个人进入厕所后首先把门锁上获取锁然后开始上厕所这个时候有其他人来了只能在外面等阻塞就算再急也没用。上完厕所完事后把门打开解锁其他人就可以进入了。CAS是乐观锁线程执行的时候不会加锁假设没有冲突去完成某项操作如果因为冲突失败了就重试最后直到成功为止。什么是 CASCASCompare-And-Swap是比较并交换的意思它是一条 CPU 并发原语用于判断内存中某个值是否为预期值如果是则更改为新的值这个过程是原子的。下面用一个小示例解释一下。CAS机制当中使用了3个基本操作数内存地址V旧的预期值A计算后要修改后的新值B。1初始状态在内存地址V中存储着变量值为 1。2线程1想要把内存地址为 V 的变量值增加1。这个时候对线程1来说旧的预期值A1要修改的新值B2。3在线程1要提交更新之前线程2捷足先登了已经把内存地址V中的变量值率先更新成了2。4线程1开始提交更新首先将预期值A和内存地址V的实际值比较Compare发现A不等于V的实际值提交失败。5线程1重新获取内存地址 V 的当前值并重新计算想要修改的新值。此时对线程1来说A2B3。这个重新尝试的过程被称为自旋。如果多次失败会有多次自旋。6线程 1 再次提交更新这一次没有其他线程改变地址 V 的值。线程1进行Compare发现预期值 A 和内存地址 V的实际值是相等的进行 Swap 操作将内存地址 V 的实际值修改为 B。总结更新一个变量的时候只有当变量的预期值 A 和内存地址 V 中的实际值相同时才会将内存地址 V 对应的值修改为 B这整个操作就是CAS。CAS 基本原理CAS 主要包括两个操作Compare和Swap有人可能要问了两个操作能保证是原子性吗可以的。CAS 是一种系统原语原语属于操作系统用语原语由若干指令组成用于完成某个功能的一个过程并且原语的执行必须是连续的在执行过程中不允许被中断也就是说 CAS 是一条 CPU 的原子指令由操作系统硬件来保证。在 Intel 的 CPU 中使用 cmpxchg 指令。回到 Java 语言JDK 是在 1.5 版本后才引入 CAS 操作在sun.misc.Unsafe这个类中定义了 CAS 相关的方法。public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);可以看到方法被声明为native如果对 C 比较熟悉可以自行下载 OpenJDK 的源码查看 unsafe.cpp这里不再展开分析。CAS 在 Java 语言中的应用在 Java 编程中我们通常不会直接使用到 CAS都是通过 JDK 封装好的并发工具类来间接使用的这些并发工具类都在java.util.concurrent包中。J.U.C 是java.util.concurrent的简称也就是大家常说的 Java 并发编程工具包面试常考非常非常重要。目前 CAS 在 JDK 中主要应用在 J.U.C 包下的 Atomic 相关类中。比如说 AtomicInteger 类就可以解决 i 非原子性问题通过查看源码可以发现主要是靠 volatile 关键字和 CAS 操作来实现具体原理和源码分析后面的文章会展开分析。CAS 的问题CAS 不是万能的也有很多问题。敲黑板CAS有哪些问题这是面试高频考点需要重点掌握。典型 ABA 问题ABA 是 CAS 操作的一个经典问题假设有一个变量初始值为 A修改为 B然后又修改为 A这个变量实际被修改过了但是 CAS 操作可能无法感知到。如果是整形还好不会影响最终结果但如果是对象的引用类型包含了多个变量引用没有变实际上包含的变量已经被修改这就会造成大问题。如何解决思路其实很简单在变量前加版本号每次变量更新了就把版本号加一结果如下最终结果都是 A 但是版本号改变了。从 JDK 1.5 开始提供了AtomicStampedReference类这个类的compareAndSe方法首先检查当前引用是否等于预期引用并且当前标志是否等于预期标志如果全部相等则以原子方式将该引用和该标志的值设置为给定的更新值。自旋开销问题CAS 出现冲突后就会开始自旋操作如果资源竞争非常激烈自旋长时间不能成功就会给 CPU 带来非常大的开销。解决方案可以考虑限制自旋的次数避免过度消耗 CPU另外还可以考虑延迟执行。只能保证单个变量的原子性当对一个共享变量执行操作时可以使用 CAS 来保证原子性但是如果要对多个共享变量进行操作时CAS 是无法保证原子性的比如需要将 i 和 j 同时加 1ij这个时候可以使用 synchronized 进行加锁有没有其他办法呢有将多个变量操作合成一个变量操作。从 JDK1.5 开始提供了AtomicReference类来保证引用对象之间的原子性你可以把多个变量放在一个对象里来进行CAS操作。有态度的总结CAS 是 Compare And Swap是一条 CPU 原语由操作系统保证原子性。Java语言从 JDK1.5 版本开始引入 CAS 并且是 Java 并发编程J.U.C 包的基石应用非常广泛。当然 CAS 也不是万能的也有很多问题典型 ABA 问题、自旋开销问题、只能保证单个变量的原子性。-- End --