一、CAS无锁并发原理全解1.1 CAS介绍1.1.1 CAS全称与定义CAS全称Compare And Swap比较并交换是非阻塞同步的实现原理是CPU硬件级别提供的无锁原子指令属于乐观锁核心实现原理JDK底层依托Unsafe类封装调用实现多线程下变量无锁安全修改。CAS三大核心操作参数内存值V、预期旧值E、更新新值N1.1.2 CAS核心核心定论CAS是乐观锁、synchronized是悲观锁二者并发设计理念完全对立CAS硬件级原子指令执行过程不可被线程中断天然保证操作原子性JDK5之后所有Atomic原子类、自旋锁、ConcurrentHashMap底层全部依托CAS实现1.2 CAS完整执行过程1.2.1 标准执行逻辑线程从主内存读取共享变量获取当前内存值V线程传入预期修改旧值E、目标修改新值NCPU原子比对内存值V 预期旧值E相等说明变量未被其他线程修改直接将内存值V更新为新值NB返回修改成功不相等说明变量已被其他线程篡改放弃本次修改返回修改失败修改失败后线程自选重试直至修改成功具体流程图如下:1.2.2 通俗案例银行卡余额当前余额100V我预期余额100E想要充值改为200N查询余额没变则修改余额变动则放弃充值重新读取余额重试。1.3 CAS代码使用方式Java无法直接调用CPU指令必须通过sun.misc.Unsafe底层类调用native本地方法执行CAS操作分为原生Unsafe调用、原子类封装调用两种方式。1.3.1 方式1原生Unsafe手动CAS实操public class CasUnsafeDemo { // 共享变量 private volatile int num 0; // 获取Unsafe底层对象 private static final Unsafe UNSAFE; // 变量内存偏移量 private static long offset; static { try { UNSAFE Unsafe.getUnsafe(); // 获取num变量内存地址偏移量 offset UNSAFE.objectFieldOffset(CasUnsafeDemo.class.getDeclaredField(num)); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) { CasUnsafeDemo demo new CasUnsafeDemo(); // CAS参数对象、偏移量、预期旧值、更新新值 boolean success UNSAFE.compareAndSwapInt(demo, offset, 0, 100); System.out.println(CAS修改结果success); // true } }以compareAndSwapInt 为例Unsafe 的compareAndSwapInt 方法接收4 个参数分别是对象实例、内存偏移量、字段期望值、字段新值。该方法会针对指定对象实例中的相应偏移量的字段执行CAS 操作。1.3.2 方式2AtomicInteger封装简化使用生产常用// 内置封装CAS无需手动操作Unsafe偏移量 AtomicInteger atomicInteger new AtomicInteger(0); // 比较并交换预期0更新为200 boolean casResult atomicInteger.compareAndSet(0,200);1.4 CAS线上生产应用场景JDK并发容器底层ConcurrentHashMap、CopyOnWriteArrayList读写CAS管控并发原子计数场景接口访问量、秒杀库存、全局序列号、线程计数器自旋锁自定义实现基于CAS实现轻量自旋锁替代短时synchronized锁乐观锁业务落地数据库版本号乐观锁、分布式本地无锁扣减线程池状态修改线程池运行/关闭状态CAS原子修改状态标识自定义并发工具实现限流计数器、令牌桶限流核心逻辑1.5 CAS底层源码深度分析JDK8 HotSpot1.5.1 Java层Unsafe入口源码// native本地方法直接对接操作系统CPU指令 public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);1.5.2 JNI本地C源码流程Unsafe调用Jni接口跳转hotspot源码unsafe.cpp封装入参调用Atomic::cmpxchg原子汇编方法追加lock总线锁指令锁定CPU总线保证多CPU核心下指令原子性执行CPU汇编指令cmpxchg 完成硬件级比较交换释放总线锁返回布尔修改结果至Java层1.5.3 核心底层要点volatile配合CASvolatile保证变量内存可见性CAS保证修改原子性二者缺一不可lock指令锁定总线禁止其他CPU同时修改共享变量解决多核并发竞争1.6 CAS核心优势对比synchronized悲观锁无线程阻塞CAS自旋重试不会进入内核态阻塞线程无线程上下文切换开销性能极高短时竞争场景CAS吞吐量远超重量级synchronized锁开销极低用户态完成操作无需内核态切换系统资源占用少粒度灵活仅针对单个共享变量修改锁粒度远小于对象锁、类锁代码轻量化原子类封装后编码简单无需手动加锁解锁1.7 CAS原生四大缺陷自旋开销大高并发竞争激烈时CAS无限重试自旋持续占用CPU资源CPU飙高只能保证单个变量原子性无法实现多个共享变量联合原子修改仅支持单变量操作无法拦截业务逻辑仅能比对变量值无法管控业务代码流程存在ABA并发问题变量值轮回篡改CAS无法感知中间修改流程判定修改无误引发业务bug1.8 ABA问题完整闭环定义、危害、解决方案1.8.1 ABA问题标准定义线程1读取内存值A准备CAS修改期间线程2将值A改为B再改回A线程1比对内存值依旧为A判定变量未修改直接执行修改忽略变量中间篡改流程即为ABA问题。1.8.2 ABA业务危害资金转账、库存扣减、链表节点修改、分布式余额场景会出现数据错乱、节点丢失、资金扣减异常高资产业务致命bug。1.8.3 ABA三大解决方案优先级排序版本戳机制最优方案新增版本号Stamp不仅比对变量值同时比对版本号每修改一次版本号自增值相同版本不同则判定已修改JDK自带AtomicStampedReference实现时间戳标记绑定变量最后修改时间双重校验值时间戳业务全局唯一标识业务层增加流水ID规避数值轮回复用1.8.4 解决ABA代码示例// 带版本戳原子引用解决ABA AtomicStampedReferenceInteger stamped new AtomicStampedReference(100,1); // 参数预期值、新值、预期版本、新版本 stamped.compareAndSet(100,200,1,2);二、Atomic原子操作类全解JDK8专属差异化在并发编程中很容易出现并发安全的问题有一个很简单的例子就是多线程更新变量i1,比如多个线程执行i操作就有可能获取不到正确的值而这个问题最常用的方法是通过Synchronized进行控制来达到线程安全的目的。但是由于synchronized是采用的是悲观锁策略并不是特别高效的一种解决方案。实际上在J.U.C下的atomic包提供了一系列的操作简单性能高效并能保证线程安全的类去更新基本类型变量数组元素引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据在java中则是使用CAS操作具体实现。2.1 JDK8六大分类原子操作类、JDK7/JDK8差异对比2.1.1 JDK8全套原子类分类基本数据类型原子类AtomicInteger、AtomicLong、AtomicBoolean引用类型原子类AtomicReference、AtomicStampedReference、AtomicMarkableReference数组原子类AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray对象字段原子类AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater累加器原子类JDK8新增DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64复合标记原子类带版本/标记防ABA专属原子类2.1.2 JDK7与JDK8原子类核心差异对比维度JDK7原子类JDK8原子类底层CAS策略单值无限自旋CAS所有线程竞争同一个变量分段Cell数组分散竞争多线程分片CAS高并发性能竞争激烈自旋耗时久CPU占用极高分散竞争自旋次数大幅降低性能提升5-10倍新增类无累加器工具类新增LongAdder、Accumulator函数式累加类内存布局无缓存行填充伪共享问题严重sun.misc.Contended缓存行填充解决伪共享2.2 全类型原子类实操示例代码可直接运行2.2.1 基本类型原子类示例// 整型原子类 AtomicInteger atomicInt new AtomicInteger(0); atomicInt.getAndIncrement(); // i atomicInt.incrementAndGet(); // i atomicInt.addAndGet(5); // 累加指定值 atomicInt.compareAndSet(5,10); // CAS修改2.2.2 引用类型防ABA原子类示例// 带版本戳彻底解决ABA User user1 new User(1,张三); User user2 new User(2,李四); // 初始值初始版本号 AtomicStampedReferenceUser atomicUser new AtomicStampedReference(user1,1); // 校验值版本双重CAS atomicUser.compareAndSet(user1,user2,1,2);2.2.3 数组原子类示例// 数组下标元素原子修改线程安全 AtomicIntegerArray intArray new AtomicIntegerArray(new int[]{1,2,3}); // 修改下标0元素预期1改为99 intArray.compareAndSet(0,1,99);2.2.4 对象字段更新原子类示例// 仅修改对象volatile字段无需封装整个对象节省内存 class Student{ public volatile int age; } // 指定类字段原子更新 AtomicIntegerFieldUpdaterStudent updater AtomicIntegerFieldUpdater.newUpdater(Student.class,age); Student student new Student(); updater.compareAndSet(student,0,18);2.2.5 JDK8新增LongAdder分段累加示例// 高并发计数首选分段累加性能碾压AtomicLong LongAdder longAdder new LongAdder(); longAdder.increment(); longAdder.add(10); // 汇总所有分段数值获取总数 long total longAdder.sum();2.3 Atomic原子操作类整体优缺点2.3.1 通用优点底层CAS无锁并发性能优于synchronized重量级锁API封装完善无需手动操作Unsafe、内存偏移量开发便捷细分场景适配齐全基础类型、数组、对象、防ABA全覆盖JDK8优化分段CAS、缓存行填充解决高并发自旋、伪共享痛点轻量无阻塞适合短时高频变量修改场景2.3.2 通用缺点基础原子类存在ABA问题业务需额外引入版本戳原子类超高并发竞争下依旧存在自旋空转、CPU占用升高问题仅支持变量原子修改无法实现多行业务代码原子性LongAdder只能做累加统计无法精准CAS修改指定数值2.4 Atomic原子类使用限制多变量联动限制无法同时原子修改多个无关共享变量多变量必须使用锁机制字段修饰限制对象字段原子更新目标字段必须被volatile修饰否则无法保证可见性访问权限限制字段更新类只能修改public/本类可访问字段私有字段无法修改功能场景限制LongAdder仅适合计数不适合精准条件修改业务线程自旋限制极端死锁竞争CAS无限自旋耗尽CPU核心资源版本维护限制防ABA原子类业务需要手动维护版本号编码复杂度提升变量类型限制只能是实例变量,只能是可修改变量2.5 Atomic原子类线上生产分级应用场景2.5.1 基础原子类场景AtomicInteger/AtomicLong低并发接口计数、单点库存扣减、简单全局自增ID、状态标识修改2.5.2 JDK8累加器场景LongAdder超高并发流量统计、网关访问量、日志计数、监控指标累加、秒杀海量计数2.5.3 防ABA引用原子类场景资金账户余额修改、链表节点并发更新、分布式本地乐观锁、核心资产数据修改2.5.4 对象字段原子更新场景大量实体类状态修改节省内存无需封装整个对象为原子类适配业务实体状态流转2.5.5 数组原子类场景并发批量下标数据统计、分片数据计数、数组点位独立修改业务