相关文章:
CAS (Compare And Swap),即比较并交换,是一种高效实现线程安全的方法,其需要三个操作数,分别是内存地址 (在 Java 中可以简单地理解为变量的内存地址,用 V
表示)、旧的预期值 (用 A
表示) 和准备设置的新值 (用 B
表示)
-
属于乐观锁机制
-
支持原子性更新操作,适用于计数器、序列发生器等场景
-
CAS 操作失败时,由我们自行决定是否继续尝试,还是执行别的操作
当执行 CAS 指令时,当且仅当 V
符合 A
时,处理器才会用 B
去更新 V
的值,否则它就不执行更新,但不管是否更新了 V
的值,最终都会返回 V
的旧值,上述处理过程为原子操作,执行期间不会被其他线程中断
一、CAS 实现
-
JUC 的 atomic 包中提供了常用的原子性数据类型以及引用、数组等相关原子类型和更新操作工具,是很多线程安全程序的首选
-
Unsafe 类虽然提供 CAS 服务,但其能够操纵任意内存地址的读写而存在隐患
-
从 Java 9 开始,可以使用 Variable Handle API 来替代 Unsafe
-
代码示例
public class CASTest { private static AtomicInteger count = new AtomicInteger(0); public static void increase() { count.getAndIncrement(); } }
- 如上所示,由于调用了
count
的原子自增方法,increase()
方法在多线程调用的情况下,不会出现线程安全问题
- 如上所示,由于调用了
二、CAS 存在的问题
-
若循环时间过长,则开销会很大
-
只能保证一个共享变量的原子操作
-
存在 ABA 的问题 (可以使用 AtomicStampedReference 来解决 ABA 的问题)
-
代码示例
public class ASRTest { public static void main(String[] args) { String V = "aaa"; String A = "aaa"; String B = "bbb"; // 将版本号设置为1 AtomicStampedReference<String> asr = new AtomicStampedReference<>(V, 1); // 进行CAS操作(A -> B) asr.compareAndSet(A, B, asr.getStamp(), asr.getStamp() + 1); // 进行CAS操作(B -> A) asr.compareAndSet(B, A, asr.getStamp(), asr.getStamp() + 1); // 此时期望的版本号为 1,而实际上版本号为 3,两者不相同,故CAS操作失败 boolean isOk = asr.compareAndSet(A, B, 1, asr.getStamp() + 1); System.out.println(isOk); // false System.out.println(asr.getStamp()); // 3 } }
- 如上所示,我们定义了一个 AtomicStampedReference 类型的变量 asr,其版本号为 1,然后模拟 ABA 操作 (即进行了两次 CAS 操作),最后想将 A 的值设置为 B,期望的版本号为 1,而实际上版本号为 3,两者不相同,故 CAS 操作失败
-
三、归纳总结
-
悲观锁
- 总是假设最坏的情况,每次取数据都认为会被其他线程修改,所以每次在取数据的时候都会加上锁
-
乐观锁
-
总是假设最好的情况,每次取数据都认为不会被其他线程修改,所以不会上锁
-
可以通过版本号机制或 CAS 算法来实现
-
-
CAS 存在的问题
-
若循环时间过长,则开销会很大
-
只能保证一个共享变量的原子操作
-
存在 ABA 问题
-
-
如何解决 CAS 中的 ABA 问题
- 使用版本号机制来解决