2025/12/28 0:32:28
网站建设
项目流程
网站系统升级,网站打开速度慢是否需要升级带宽,长沙网站搭建,用html做的网站步骤在Java并发编程领域#xff0c;除了synchronized这种基于锁的同步机制外#xff0c;还有一种更轻量级的方案——CAS#xff08;Compare-And-Swap#xff0c;比较并交换#xff09;。CAS是无锁编程的核心思想#xff0c;而JUC#xff08;java.util.concurrent#xff09…在Java并发编程领域除了synchronized这种基于锁的同步机制外还有一种更轻量级的方案——CASCompare-And-Swap比较并交换。CAS是无锁编程的核心思想而JUCjava.util.concurrent包下的原子类则是CAS思想的最佳实践。本文将从CAS的底层原理出发拆解JUC原子类的设计、解决的问题如ABA、性能优化手段以及CAS的应用场景和局限性帮助你掌握无锁并发的核心技术。一、CAS无锁编程的基石1.1 什么是CASCAS是一种原子指令由CPU硬件层面保证原子性其核心思想是在更新变量值之前先比较变量当前值是否与预期值一致如果一致就将变量更新为新值如果不一致就放弃更新并重试。与synchronized的悲观锁思维假设一定会有线程竞争先加锁再执行不同CAS是典型的乐观锁思维假设不会有线程竞争直接尝试更新只有发现竞争时才通过重试自旋解决问题全程无需加锁。1.2 CAS的核心操作步骤CAS操作包含三个核心参数V要更新的变量内存地址A预期值线程认为变量当前应该有的值B新值线程想要将变量更新为的值。完整执行流程线程读取变量V的当前值记为当前值判断当前值是否等于预期值A如果相等将变量V的值更新为新值B操作成功如果不相等说明有其他线程修改了变量放弃更新重新执行步骤1自旋重试重复上述过程直到操作成功或达到重试阈值后放弃。1.3 CAS的Java实现示例Java中通过Unsafe类提供直接操作内存的能力调用CAS指令以下是简化的CAS操作示例importsun.misc.Unsafe;importjava.lang.reflect.Field;publicclassCASDemo{// 要更新的共享变量privatevolatileintvalue0;privatestaticfinalUnsafeunsafe;privatestaticfinallongvalueOffset;// 静态代码块获取Unsafe实例和value字段的内存偏移量static{try{// 通过反射获取Unsafe实例Unsafe的构造方法是私有的FieldunsafeFieldUnsafe.class.getDeclaredField(theUnsafe);unsafeField.setAccessible(true);unsafe(Unsafe)unsafeField.get(null);// 获取value字段在CASDemo对象中的内存偏移量FieldvalueFieldCASDemo.class.getDeclaredField(value);valueOffsetunsafe.objectFieldOffset(valueField);}catch(Exceptione){thrownewRuntimeException(e);}}// 自定义CAS操作publicbooleancompareAndSwap(intexpect,intupdate){// Unsafe的compareAndSwapInt方法CAS核心实现// 参数对象实例、字段偏移量、预期值、新值returnunsafe.compareAndSwapInt(this,valueOffset,expect,update);}// 自旋CAS实现自增publicvoidincrement(){intcurrent;do{// 获取当前值预期值currentthis.value;// 尝试CAS更新预期值是current新值是current1}while(!compareAndSwap(current,current1));}publicintgetValue(){returnvalue;}publicstaticvoidmain(String[]args)throwsInterruptedException{CASDemodemonewCASDemo();// 启动10个线程每个线程自增1000次for(inti0;i10;i){newThread(()-{for(intj0;j1000;j){demo.increment();}}).start();}// 等待所有线程执行完成Thread.sleep(1000);System.out.println(最终值demo.getValue());// 输出10000线程安全}}1.4 CAS与synchronized的性能对比特性CASsynchronized锁类型乐观锁无锁悲观锁有锁线程阻塞无阻塞自旋重试可能阻塞重量级锁适用场景高并发、低冲突高并发、高冲突性能开销冲突低时开销极小始终有锁的获取/释放开销资源消耗冲突高时CPU自旋消耗大冲突高时线程阻塞CPU消耗低核心结论高并发、低冲突场景如少量线程更新共享变量CAS更优无锁切换开销性能远超synchronized高并发、高冲突场景如大量线程争抢同一个变量CAS会导致大量线程空自旋CPU利用率飙升此时synchronized重量级锁更稳定线程阻塞避免无效自旋。二、JUC原子类CAS的开箱即用实现JDK提供了java.util.concurrent.atomic包封装了各种基于CAS的原子类无需我们手动实现CAS自旋直接解决共享变量的线程安全问题。2.1 JUC原子类的分类JUC原子类主要分为四大类覆盖不同类型的共享变量操作2.1.1 基本类型原子类AtomicInteger原子更新int类型AtomicLong原子更新long类型AtomicBoolean原子更新boolean类型。核心方法以AtomicInteger为例AtomicIntegercountnewAtomicInteger(0);count.getAndIncrement();// 先获取值再自增icount.incrementAndGet();// 先自增再获取值icount.getAndAdd(5);// 先获取值再加5count.compareAndSet(0,1);// CAS操作预期值0新值12.1.2 数组类型原子类AtomicIntegerArray原子更新int数组的元素AtomicLongArray原子更新long数组的元素AtomicReferenceArray原子更新引用类型数组的元素。示例int[]arr{1,2,3};AtomicIntegerArrayatomicArrnewAtomicIntegerArray(arr);// 原子更新索引0的元素预期值1新值10atomicArr.compareAndSet(0,1,10);System.out.println(atomicArr.get(0));// 输出102.1.3 引用类型原子类AtomicReference原子更新引用类型解决多个共享变量的原子性问题AtomicStampedReference解决ABA问题的引用类型带版本号AtomicMarkableReference解决ABA问题的引用类型带标记。2.1.4 字段更新器原子类AtomicIntegerFieldUpdater原子更新对象的int字段AtomicLongFieldUpdater原子更新对象的long字段AtomicReferenceFieldUpdater原子更新对象的引用字段。特点可以原子更新对象的非静态字段要求字段必须是volatile修饰的。2.2 CAS的经典问题ABA问题2.2.1 什么是ABA问题ABA问题是CAS的天然缺陷线程1准备更新变量值时发现变量当前值是A预期值但在此期间变量可能被线程2修改为B又被线程3改回A。此时线程1的CAS操作会认为变量未被修改成功更新但实际上变量已经被修改过可能导致逻辑错误。示例场景线程1读取变量xA准备CAS更新为C线程2抢占CPU将x从A改为B线程3又将x从B改回A线程1恢复执行发现xA与预期值一致执行CAS将x改为C。线程1的CAS操作成功但x实际上被修改过两次这就是ABA问题。2.2.2 ABA问题的解决方案核心思路为变量增加版本号或标记不仅比较值还要比较版本号/标记。方案1版本号AtomicStampedReferenceAtomicStampedReference为每个变量绑定一个版本号stampCAS操作时同时比较值和版本号只有两者都匹配时才更新。示例importjava.util.concurrent.atomic.AtomicStampedReference;publicclassABADemo{publicstaticvoidmain(String[]args){// 初始化值为A版本号为1AtomicStampedReferenceStringasrnewAtomicStampedReference(A,1);// 线程1准备CAS更新为C先获取当前值和版本号StringexpectValueasr.getReference();intexpectStampasr.getStamp();System.out.println(线程1-预期值expectValue预期版本号expectStamp);// 线程2模拟ABA操作newThread(()-{// 版本号1将A改为Basr.compareAndSet(A,B,asr.getStamp(),asr.getStamp()1);System.out.println(线程2-将A改为B版本号asr.getStamp());// 版本号1将B改回Aasr.compareAndSet(B,A,asr.getStamp(),asr.getStamp()1);System.out.println(线程2-将B改回A版本号asr.getStamp());}).start();// 等待线程2执行完成try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}// 线程1执行CAS值是A但版本号已经变了从1变为3booleanresultasr.compareAndSet(expectValue,C,expectStamp,expectStamp1);System.out.println(线程1-CAS结果result);// 输出falseSystem.out.println(最终值asr.getReference()最终版本号asr.getStamp());}}输出结果线程1-预期值A预期版本号1 线程2-将A改为B版本号2 线程2-将B改回A版本号3 线程1-CAS结果false 最终值A最终版本号3线程1的CAS操作失败因为版本号不匹配成功解决ABA问题。方案2标记位AtomicMarkableReferenceAtomicMarkableReference为变量绑定一个布尔标记mark用于标记变量是否被修改过适用于只需判断是否修改无需记录版本号的场景。示例importjava.util.concurrent.atomic.AtomicMarkableReference;publicclassAtomicMarkableDemo{publicstaticvoidmain(String[]args){// 初始化值为A标记为false未被修改AtomicMarkableReferenceStringamrnewAtomicMarkableReference(A,false);// 线程2修改值newThread(()-{// 将A改为B标记为true已修改amr.compareAndSet(A,B,false,true);// 将B改回A标记保持true已修改amr.compareAndSet(B,A,true,true);}).start();try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}// 线程1尝试CAS预期值A预期标记falsebooleanresultamr.compareAndSet(A,C,false,true);System.out.println(CAS结果result);// 输出false}}2.3 高并发下CAS的性能优化CAS的核心问题是竞争激烈时大量线程空自旋导致CPU利用率飙升性能下降。JDK针对这个问题提供了两种优化思路2.3.1 空间换时间LongAdder替代AtomicLongAtomicLong在高并发下所有线程争抢同一个变量的CAS操作导致自旋次数剧增。LongAdder的核心优化是将一个全局变量拆分为多个局部变量cell数组线程更新时先通过哈希算法定位到某个cell更新该cell的值读取全局值时累加所有cell的值 基值base。原理示意图LongAdder { base: 0 // 基础值竞争小时直接更新base cells: [cell1, cell2, cell3...] // 竞争大时分散到不同cell }使用示例importjava.util.concurrent.atomic.LongAdder;publicclassLongAdderDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{LongAdderaddernewLongAdder();// 启动100个线程每个线程自增10000次for(inti0;i100;i){newThread(()-{for(intj0;j10000;j){adder.increment();// 原子自增}}).start();}Thread.sleep(2000);System.out.println(最终值adder.sum());// 输出1000000}}核心优势低竞争时直接更新base性能与AtomicLong持平高竞争时分散到多个cell减少CAS冲突性能远超AtomicLong。2.3.2 队列削峰CLH队列AQS核心另一种优化思路是将争抢CAS的线程加入队列排队避免同时自旋。典型代表是AQSAbstractQueuedSynchronizer其核心逻辑线程尝试CAS获取资源如果失败将线程封装为节点加入CLH队列尾部队列中的线程不再空自旋而是通过前驱节点通知的方式唤醒减少CPU消耗。这种方式本质是将并发争抢转为串行排队降低CAS冲突的激烈程度是ReentrantLock、CountDownLatch等JUC工具的底层实现。三、CAS的应用场景与局限性3.1 CAS的核心应用3.1.1 JUC原子类如AtomicInteger、LongAdder等是CAS最直接的应用解决基本类型/引用类型的原子更新问题。3.1.2 AQS抽象队列同步器AQS是JUC并发工具的基石ReentrantLock、Semaphore、CountDownLatch等都基于AQS其核心是通过CAS操作更新同步状态state变量并通过CLH队列管理等待线程。3.1.3 ConcurrentHashMapJDK1.8的ConcurrentHashMap放弃了分段锁改用CAS synchronized实现并发安全初始化桶Node数组时用CAS保证原子性插入元素时先尝试CAS更新失败后再用synchronized锁定桶节点扩容时用CAS标记扩容状态避免多线程重复扩容。3.2 CAS的局限性3.2.1 ABA问题如前文所述可通过AtomicStampedReference/AtomicMarkableReference解决。3.2.2 只能保证单个变量的原子性CAS只能对一个共享变量执行原子操作如果需要同时更新多个共享变量可通过以下方式解决将多个变量封装为一个对象用AtomicReference原子更新对象引用使用锁synchronized/ReentrantLock保证多个变量操作的原子性。示例AtomicReference更新多个变量importjava.util.concurrent.atomic.AtomicReference;// 封装多个变量为对象classUser{privateintid;privateStringname;publicUser(intid,Stringname){this.idid;this.namename;}// 省略getter/setter}publicclassAtomicReferenceDemo{publicstaticvoidmain(String[]args){Useruser1newUser(1,张三);Useruser2newUser(2,李四);AtomicReferenceUseratomicRefnewAtomicReference(user1);// 原子更新整个User对象同时更新id和namebooleanresultatomicRef.compareAndSet(user1,user2);System.out.println(result);// 输出trueSystem.out.println(atomicRef.get().getId():atomicRef.get().getName());// 输出2:李四}}3.2.3 无效自旋导致CPU开销大高并发、高冲突场景下大量线程自旋重试CAS操作会导致CPU利用率达到100%系统响应变慢。解决方案限制自旋次数如AQS默认自旋10次后加入队列使用LongAdder替代AtomicLong冲突极高时改用synchronized重量级锁。3.2.4 总线风暴CAS是CPU的原子指令执行时会锁定总线MESI协议大量CAS操作会导致总线带宽被占满其他CPU核心无法正常通信即总线风暴。解决方案减少CAS操作的频率如批量更新、分散竞争。四、总结核心知识点回顾CAS的本质乐观锁思想通过CPU原子指令实现比较-交换无锁但可能自旋适用于高并发低冲突场景。JUC原子类CAS的开箱即用实现分为基本类型、数组类型、引用类型、字段更新器四类解决共享变量的原子更新问题。ABA问题CAS的经典缺陷可通过版本号AtomicStampedReference或标记位AtomicMarkableReference解决。CAS性能优化高并发下用LongAdder空间换时间或队列排队AQS减少CAS冲突避免空自旋。CAS的局限性存在ABA问题、仅支持单个变量原子性、高冲突时CPU开销大、可能引发总线风暴。实战建议低并发场景优先使用AtomicInteger/AtomicLong简单高效高并发计数场景用LongAdder替代AtomicLong大幅提升性能需解决ABA问题使用AtomicStampedReference记录版本或AtomicMarkableReference记录标记多变量原子更新用AtomicReference封装对象或直接使用锁极高冲突场景放弃CAS改用synchronizedJDK1.6后优化性能已大幅提升。CAS和JUC原子类是Java无锁并发的核心掌握其原理和使用场景能让你在高并发编程中灵活选择同步方案既保证线程安全又最大化系统性能。