三、CAS 与 Atomic

三、CAS 与 Atomic
├── 三、CAS 与 Atomic│ ├── CAS 原理比较内存中的值是否还是自己读取时的值如果是就修改否则失败再重新尝试。a(希望的值修改的值建议 内存当前的值是否是希望的值是就修改CAS 的比较 修改并不是两步操作而是CPU 提供的一条原子指令如 x86 的CMPXCHG完成的。参数含义举例V内存中的真实值value5A期望值Expected线程读取到5B新值New修改成6线程读取 value5│▼准备修改为6│▼CPU执行CAS│▼value还是5│┌─┴────────┐│ │是 否│ │修改成功 修改失败│ │结束 重新读取CAS 为什么线程安全CAS 是**乐观锁Optimistic Lock**思想的典型实现认为大多数情况下不会发生冲突冲突时再重试。CAS 底层原理AtomicInteger.incrementAndGet();Unsafe.compareAndSwapInt()VarHandle.compareAndSet()CPU 原子指令 CMPXCHGx86AtomicInteger│▼Unsafe / VarHandle│▼JNI│▼CPU CAS 指令│▼修改内存自旋优点缺点无锁性能高高竞争时不断重试浪费 CPU不会发生线程阻塞存在 ABA 问题CPU 原子指令保证安全只能保证单个共享变量的原子性高并发效率高多变量需要额外机制如AtomicReference、锁等│ ├── ABA 问题ABA 问题是 CAS 的经典缺陷。CAS 只判断当前值是否等于期望值并不知道这个值在中间是否被别人修改过。A↓B↓A值虽然又变回了A但实际上已经发生过变化。CAS 看到还是A会误以为没人修改过于是修改成功。解决方式版本号valueA version1虽然值一样但是版本已经变化。CAS 能发现数据被修改过。JDK 提供AtomicStampedReference值 版本号AtomicMarkableReference值 标记位其中最常用的是AtomicStampedReference每次更新同时比较引用和值对应的版本号Stamp只要版本号变化即使值恢复原样CAS 也会失败。ABA 问题是 CAS 的典型缺陷。由于 CAS 只比较当前值是否等于期望值而不会检查该值在中间是否发生过变化因此当数据经历 A→B→A 后CAS 仍会认为数据未被修改。Java 通常通过AtomicStampedReference引入版本号Stamp将比较对象从值扩展为值 版本号从而避免 ABA 问题。超级多类│ ├── AtomicReference│ ├── AtomicStampedReference│ ├── AtomicMarkableReference│ ├── AtomicIntegerFieldUpdater│ └── Unsafe 类类操作对象主要作用解决什么问题是否解决 ABA典型场景AtomicReferenceT引用对象原子更新对象引用普通 CAS 只能更新基本类型不能方便地原子更新对象引用❌无锁链表、无锁队列、缓存对象切换AtomicStampedReferenceT引用 版本号在 CAS 的基础上增加版本号Stamp解决 ABA 问题✅链表、栈、队列等需要检测引用是否被修改过AtomicMarkableReferenceT引用 标记位给对象增加一个布尔标记true/false标记对象状态而不是记录修改次数⚠️ 部分避免不能完全解决逻辑删除、是否已处理、是否失效AtomicIntegerFieldUpdaterT对象中的int字段不创建AtomicInteger直接原子更新对象字段减少内存占用❌大量对象中的计数器、状态字段Unsafe任意内存提供底层 CAS、内存操作、对象创建等能力JVM 底层实现并发工具取决于使用方式JUC、Netty、Spring、JVM 底层可见性