Java并发编程知识点一、并发容器和框架解决手动同步的痛点1.1 核心定义在Java多线程编程中并发容器是java.util.concurrent包下提供的线程安全数据结构内部已实现完善的同步机制或非阻塞算法开发者无需手动加锁即可在多线程环境下安全操作数据。并发框架则是用于简化并发任务执行的组件集合包括线程池、ForkJoin框架、同步工具类等。1.2 三大核心优势降低开发难度避免手写同步代码带来的死锁、数据竞争等bug性能更优经过JDK团队高度优化采用分段锁、CAS等技术远优于粗暴的synchronized全表加锁功能丰富覆盖了从数据存储到任务执行的全场景并发需求1.3 整体架构概览java.util.concurrent ├── 并发容器 │ ├── 集合类ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentSkipListMap │ └── 队列类BlockingQueue、ConcurrentLinkedQueue ├── 并发框架 │ ├── 线程池ThreadPoolExecutor、ScheduledThreadPoolExecutor │ └── ForkJoin框架ForkJoinPool、RecursiveTask、RecursiveAction └── 工具类 ├── 原子类AtomicInteger、AtomicReference └── 同步工具CountDownLatch、CyclicBarrier、Semaphore二、哈希表三巨头HashMap、Hashtable与ConcurrentHashMap这是Java面试100%必考点必须彻底掌握三者的区别和底层实现。2.1 核心对比表特性HashMapHashtableConcurrentHashMap线程安全❌ 非线程安全✅ 线程安全全表锁✅ 线程安全精细化锁性能极高极差单线程访问高并发度高空键/空值允许一个null键多个null值不允许任何null键或null值不允许任何null键或null值底层结构JDK1.7数组链表JDK1.8数组链表红黑树数组链表JDK1.7分段锁Segment数组JDK1.8数组链表红黑树CASsynchronized推荐场景单线程环境已废弃不推荐使用多线程并发环境2.2 HashMap的进化与线程安全问题JDK 1.7采用头插法插入元素多线程扩容时会导致链表形成环形结构后续get()操作会陷入死循环CPU使用率飙升至100%JDK 1.8改为尾插法解决了环形链表问题但仍存在数据覆盖等线程安全问题。例如两个线程同时执行put()操作可能导致其中一个线程的数据被覆盖2.3 ConcurrentHashMap的实现原理面试重中之重JDK 1.7分段锁机制内部维护一个Segment数组每个Segment继承自ReentrantLock相当于一个独立的小哈希表加锁粒度是Segment级别不同Segment的操作可以完全并发执行缺点最多支持16个线程同时写默认Segment数量为16并发度有限JDK 1.8CAS节点级锁彻底取消了Segment改为对每个数组节点桶进行加锁当桶为空时使用CAS操作插入元素无需加锁当桶不为空时使用synchronized锁定桶的头节点引入红黑树优化长链表查询与HashMap保持一致优势并发度大幅提升理论上支持数组长度级别的并发写2.4 面试高频追问为什么ConcurrentHashMap不允许null键和null值为了避免歧义。如果get(key)返回null无法判断是key不存在还是value本身就是null在并发环境下这个问题会被放大。ConcurrentHashMap的size()方法如何实现JDK1.8采用先尝试无锁统计失败后再加锁统计的方式避免了1.7版本中加锁统计的性能问题。三、哈希表底层原理与散列思想3.1 哈希表的核心逻辑哈希表之所以能实现O(1)时间复杂度的增删改查核心在于散列技术通过哈希函数将任意长度的键key转换为固定长度的哈希值将哈希值对数组长度取模得到元素应该存放的**桶bucket**位置如果多个元素映射到同一个桶使用链地址法解决冲突链表或红黑树3.2 HashMap的哈希函数设计JDK 1.8staticfinalinthash(Objectkey){inth;return(keynull)?0:(hkey.hashCode())^(h16);}将key的hashCode高16位与低16位进行异或运算目的让高位也参与到桶位置的计算中减少哈希冲突使元素分布更均匀3.3 散列思想的工程应用散列不仅用于哈希表更是分布式系统的核心思想数据库分库分表对主键哈希后取模将数据分散到不同库表负载均衡对客户端IP哈希后分配到不同服务器一致性哈希解决分布式缓存的节点动态变化问题四、HashMap多线程死循环问题深度解析4.1 问题根源JDK 1.7的头插法扩容当HashMap元素数量超过阈值容量×负载因子默认0.75时会触发扩容创建一个容量为原来2倍的新数组遍历旧数组将每个元素重新哈希到新数组JDK 1.7采用头插法将旧链表的节点依次插入新链表的头部4.2 环形链表形成过程假设两个线程同时对同一个HashMap进行扩容线程A执行到扩容代码的中间位置被挂起线程B完成扩容将旧链表的节点重新排列线程A恢复执行继续按照原来的指针遍历由于头插法的特性两个节点会互相指向对方形成环形链表4.3 后果与解决方案后果后续调用get()方法查询该桶中的元素时会陷入无限循环CPU使用率飙升至100%解决方案多线程环境下绝对不要使用HashMap使用ConcurrentHashMap替代如果必须使用HashMap可以用Collections.synchronizedMap()包装性能较差五、阻塞队列生产者消费者模式的最佳实践5.1 核心特性阻塞队列BlockingQueue是一种支持两个阻塞操作的队列当队列满时入队操作会阻塞直到队列有空闲空间当队列空时出队操作会阻塞直到队列有元素5.2 常见实现类对比实现类数据结构有界性特点适用场景ArrayBlockingQueue数组有界必须指定容量公平/非公平锁可选大多数生产环境LinkedBlockingQueue链表可选有界默认无界吞吐量高于ArrayBlockingQueue需注意内存溢出风险SynchronousQueue无存储特殊不存储元素每个入队必须等待一个出队线程池newCachedThreadPoolPriorityBlockingQueue堆无界支持优先级排序需要按优先级处理任务的场景5.3 有界队列与无界队列的权衡无界队列的风险生产者速度远快于消费者时任务会无限堆积最终导致OutOfMemoryError在线程池中使用无界队列时线程池永远不会创建超过核心线程数的线程任务响应时间会越来越长有界队列的优势可以控制系统的资源使用避免内存溢出当队列满时可以触发线程池扩容或执行拒绝策略保证系统的可控性最佳实践生产环境中必须使用有界队列并根据业务场景合理设置队列容量。5.4 代码示例阻塞队列实现生产者消费者importjava.util.concurrent.ArrayBlockingQueue;importjava.util.concurrent.BlockingQueue;publicclassBlockingQueueDemo{privatestaticfinalBlockingQueueIntegerqueuenewArrayBlockingQueue(5);publicstaticvoidmain(String[]args){// 启动2个生产者线程for(inti0;i2;i){newThread(newProducer(),Producer-i).start();}// 启动3个消费者线程for(inti0;i3;i){newThread(newConsumer(),Consumer-i).start();}}staticclassProducerimplementsRunnable{Overridepublicvoidrun(){intnum0;while(true){try{queue.put(num);// 队列满时阻塞System.out.println(Thread.currentThread().getName()生产num);num;Thread.sleep(500);}catch(InterruptedExceptione){Thread.currentThread().interrupt();break;}}}}staticclassConsumerimplementsRunnable{Overridepublicvoidrun(){while(true){try{intnumqueue.take();// 队列空时阻塞System.out.println(Thread.currentThread().getName()消费num);Thread.sleep(1000);}catch(InterruptedExceptione){Thread.currentThread().interrupt();break;}}}}}六、ForkJoin框架分而治之的并行计算利器6.1 核心思想ForkJoin框架是Java 7引入的并行计算框架基于分而治之的思想Fork将一个大任务递归拆分成多个足够小的子任务Join并行执行所有子任务然后合并子任务的结果得到最终结果6.2 工作窃取算法Work-StealingForkJoin框架的高效性得益于工作窃取算法每个工作线程都有自己的双端队列用于存储分配给自己的任务当一个线程完成自己队列中的所有任务后会从其他线程队列的尾部窃取任务执行这种方式可以有效减少线程竞争提高CPU利用率6.3 代码示例计算1到n的和importjava.util.concurrent.ForkJoinPool;importjava.util.concurrent.RecursiveTask;publicclassForkJoinDemoextendsRecursiveTaskLong{privatestaticfinallongTHRESHOLD1000;// 任务拆分阈值privatefinallongstart;privatefinallongend;publicForkJoinDemo(longstart,longend){this.startstart;this.endend;}OverrideprotectedLongcompute(){// 如果任务足够小直接计算if(end-startTHRESHOLD){longsum0;for(longistart;iend;i){sumi;}returnsum;}// 否则拆分成两个子任务longmid(startend)/2;ForkJoinDemoleftTasknewForkJoinDemo(start,mid);ForkJoinDemorightTasknewForkJoinDemo(mid1,end);// 执行子任务leftTask.fork();rightTask.fork();// 合并子任务结果returnleftTask.join()rightTask.join();}publicstaticvoidmain(String[]args){ForkJoinPoolpoolnewForkJoinPool();longresultpool.invoke(newForkJoinDemo(1,1000000));System.out.println(计算结果result);}}6.4 适用场景与限制适用场景CPU密集型任务尤其是递归分治类任务如排序、矩阵运算、大数据处理不适用场景IO密集型任务线程会阻塞无法充分利用CPU注意事项子任务中不能执行阻塞操作否则会导致工作线程无法执行其他任务七、原子类与CAS机制无锁线程安全的实现7.1 原子类分类java.util.concurrent.atomic包提供了多种原子类用于实现无锁的线程安全操作基本类型AtomicInteger、AtomicLong、AtomicBoolean引用类型AtomicReference、AtomicStampedReference、AtomicMarkableReference数组类型AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray字段更新器AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater7.2 CAS机制详解CASCompare-And-Swap比较并交换是一种CPU原语是实现原子类的基础它包含三个操作数内存地址V、旧的预期值A、新值B当且仅当内存地址V中的值等于预期值A时将V的值更新为B整个操作是原子的不会被其他线程中断AtomicInteger的incrementAndGet()方法内部就是一个典型的CAS自旋publicfinalintincrementAndGet(){returnunsafe.getAndAddInt(this,valueOffset,1)1;}// Unsafe类中的getAndAddInt方法publicfinalintgetAndAddInt(Objecto,longoffset,intdelta){intv;do{vgetIntVolatile(o,offset);// 读取当前值}while(!compareAndSwapInt(o,offset,v,vdelta));// CAS尝试更新returnv;}7.3 CAS的三大问题ABA问题一个值从A变为B又变回ACAS会认为没有变化但实际上已经发生了变化解决方案使用AtomicStampedReference带版本号或AtomicMarkableReference带标记位自旋开销大高并发下CAS会频繁失败导致大量自旋重试浪费CPU资源解决方案高竞争场景下使用锁替代只能保证单个变量的原子性无法同时保证多个变量的原子操作解决方案使用锁或将多个变量封装成一个对象使用AtomicReference7.4 ABA问题代码示例importjava.util.concurrent.atomic.AtomicStampedReference;publicclassABADemo{privatestaticfinalAtomicStampedReferenceIntegerrefnewAtomicStampedReference(100,0);publicstaticvoidmain(String[]args)throwsInterruptedException{// 线程1执行ABA操作Threadt1newThread(()-{intstampref.getStamp();System.out.println(线程1初始版本号stamp);ref.compareAndSet(100,101,stamp,stamp1);ref.compareAndSet(101,100,ref.getStamp(),ref.getStamp()1);});// 线程2尝试更新Threadt2newThread(()-{intstampref.getStamp();System.out.println(线程2初始版本号stamp);try{Thread.sleep(1000);// 等待线程1完成ABA操作}catch(InterruptedExceptione){e.printStackTrace();}booleansuccessref.compareAndSet(100,200,stamp,stamp1);System.out.println(线程2更新是否成功success);System.out.println(当前版本号ref.getStamp());});t1.start();t2.start();t1.join();t2.join();}}执行结果线程1初始版本号0 线程2初始版本号0 线程2更新是否成功false 当前版本号2八、同步工具类CountDownLatch与CyclicBarrier8.1 CountDownLatch一次性计数器作用允许一个或多个线程等待其他线程完成一组操作原理通过一个计数器实现初始值为需要等待的线程数核心方法countDown()计数器减1await()等待计数器变为0特点一次性使用计数器变为0后无法重置8.2 CyclicBarrier循环屏障作用允许一组线程互相等待直到所有线程都到达屏障点原理通过一个计数器实现初始值为参与等待的线程数核心方法await()线程到达屏障点等待其他线程reset()重置屏障可以重复使用特点可循环使用支持在所有线程到达时执行一个额外的Runnable任务8.3 核心区别对比表特性CountDownLatchCyclicBarrier等待关系主线程等待子线程子线程互相等待计数器只能减少一次性使用可以重置循环使用额外任务不支持支持在所有线程到达时执行适用场景等待多个任务完成后汇总结果并行迭代计算多线程同步执行8.4 CyclicBarrier代码示例模拟运动员赛跑importjava.util.concurrent.CyclicBarrier;publicclassCyclicBarrierDemo{publicstaticvoidmain(String[]args){intrunnerCount5;// 创建CyclicBarrier当所有运动员到达后执行发令枪任务CyclicBarrierbarriernewCyclicBarrier(runnerCount,()-{System.out.println(所有运动员准备就绪发令枪响);});for(inti0;irunnerCount;i){newThread(newRunner(barrier),运动员(i1)).start();}}staticclassRunnerimplementsRunnable{privatefinalCyclicBarrierbarrier;publicRunner(CyclicBarrierbarrier){this.barrierbarrier;}Overridepublicvoidrun(){try{System.out.println(Thread.currentThread().getName()正在准备...);Thread.sleep((long)(Math.random()*1000));// 模拟准备时间System.out.println(Thread.currentThread().getName()准备完毕);barrier.await();// 等待其他运动员System.out.println(Thread.currentThread().getName()开始跑步);}catch(Exceptione){e.printStackTrace();}}}}总结本文总结了Java并发容器和框架中最核心的面试知识点涵盖了并发容器、阻塞队列、ForkJoin框架、原子类与CAS、同步工具类等多个方面。在面试中不仅要记住这些概念更要理解其底层原理和适用场景并能结合代码示例进行说明。
并发编程 七
Java并发编程知识点一、并发容器和框架解决手动同步的痛点1.1 核心定义在Java多线程编程中并发容器是java.util.concurrent包下提供的线程安全数据结构内部已实现完善的同步机制或非阻塞算法开发者无需手动加锁即可在多线程环境下安全操作数据。并发框架则是用于简化并发任务执行的组件集合包括线程池、ForkJoin框架、同步工具类等。1.2 三大核心优势降低开发难度避免手写同步代码带来的死锁、数据竞争等bug性能更优经过JDK团队高度优化采用分段锁、CAS等技术远优于粗暴的synchronized全表加锁功能丰富覆盖了从数据存储到任务执行的全场景并发需求1.3 整体架构概览java.util.concurrent ├── 并发容器 │ ├── 集合类ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentSkipListMap │ └── 队列类BlockingQueue、ConcurrentLinkedQueue ├── 并发框架 │ ├── 线程池ThreadPoolExecutor、ScheduledThreadPoolExecutor │ └── ForkJoin框架ForkJoinPool、RecursiveTask、RecursiveAction └── 工具类 ├── 原子类AtomicInteger、AtomicReference └── 同步工具CountDownLatch、CyclicBarrier、Semaphore二、哈希表三巨头HashMap、Hashtable与ConcurrentHashMap这是Java面试100%必考点必须彻底掌握三者的区别和底层实现。2.1 核心对比表特性HashMapHashtableConcurrentHashMap线程安全❌ 非线程安全✅ 线程安全全表锁✅ 线程安全精细化锁性能极高极差单线程访问高并发度高空键/空值允许一个null键多个null值不允许任何null键或null值不允许任何null键或null值底层结构JDK1.7数组链表JDK1.8数组链表红黑树数组链表JDK1.7分段锁Segment数组JDK1.8数组链表红黑树CASsynchronized推荐场景单线程环境已废弃不推荐使用多线程并发环境2.2 HashMap的进化与线程安全问题JDK 1.7采用头插法插入元素多线程扩容时会导致链表形成环形结构后续get()操作会陷入死循环CPU使用率飙升至100%JDK 1.8改为尾插法解决了环形链表问题但仍存在数据覆盖等线程安全问题。例如两个线程同时执行put()操作可能导致其中一个线程的数据被覆盖2.3 ConcurrentHashMap的实现原理面试重中之重JDK 1.7分段锁机制内部维护一个Segment数组每个Segment继承自ReentrantLock相当于一个独立的小哈希表加锁粒度是Segment级别不同Segment的操作可以完全并发执行缺点最多支持16个线程同时写默认Segment数量为16并发度有限JDK 1.8CAS节点级锁彻底取消了Segment改为对每个数组节点桶进行加锁当桶为空时使用CAS操作插入元素无需加锁当桶不为空时使用synchronized锁定桶的头节点引入红黑树优化长链表查询与HashMap保持一致优势并发度大幅提升理论上支持数组长度级别的并发写2.4 面试高频追问为什么ConcurrentHashMap不允许null键和null值为了避免歧义。如果get(key)返回null无法判断是key不存在还是value本身就是null在并发环境下这个问题会被放大。ConcurrentHashMap的size()方法如何实现JDK1.8采用先尝试无锁统计失败后再加锁统计的方式避免了1.7版本中加锁统计的性能问题。三、哈希表底层原理与散列思想3.1 哈希表的核心逻辑哈希表之所以能实现O(1)时间复杂度的增删改查核心在于散列技术通过哈希函数将任意长度的键key转换为固定长度的哈希值将哈希值对数组长度取模得到元素应该存放的**桶bucket**位置如果多个元素映射到同一个桶使用链地址法解决冲突链表或红黑树3.2 HashMap的哈希函数设计JDK 1.8staticfinalinthash(Objectkey){inth;return(keynull)?0:(hkey.hashCode())^(h16);}将key的hashCode高16位与低16位进行异或运算目的让高位也参与到桶位置的计算中减少哈希冲突使元素分布更均匀3.3 散列思想的工程应用散列不仅用于哈希表更是分布式系统的核心思想数据库分库分表对主键哈希后取模将数据分散到不同库表负载均衡对客户端IP哈希后分配到不同服务器一致性哈希解决分布式缓存的节点动态变化问题四、HashMap多线程死循环问题深度解析4.1 问题根源JDK 1.7的头插法扩容当HashMap元素数量超过阈值容量×负载因子默认0.75时会触发扩容创建一个容量为原来2倍的新数组遍历旧数组将每个元素重新哈希到新数组JDK 1.7采用头插法将旧链表的节点依次插入新链表的头部4.2 环形链表形成过程假设两个线程同时对同一个HashMap进行扩容线程A执行到扩容代码的中间位置被挂起线程B完成扩容将旧链表的节点重新排列线程A恢复执行继续按照原来的指针遍历由于头插法的特性两个节点会互相指向对方形成环形链表4.3 后果与解决方案后果后续调用get()方法查询该桶中的元素时会陷入无限循环CPU使用率飙升至100%解决方案多线程环境下绝对不要使用HashMap使用ConcurrentHashMap替代如果必须使用HashMap可以用Collections.synchronizedMap()包装性能较差五、阻塞队列生产者消费者模式的最佳实践5.1 核心特性阻塞队列BlockingQueue是一种支持两个阻塞操作的队列当队列满时入队操作会阻塞直到队列有空闲空间当队列空时出队操作会阻塞直到队列有元素5.2 常见实现类对比实现类数据结构有界性特点适用场景ArrayBlockingQueue数组有界必须指定容量公平/非公平锁可选大多数生产环境LinkedBlockingQueue链表可选有界默认无界吞吐量高于ArrayBlockingQueue需注意内存溢出风险SynchronousQueue无存储特殊不存储元素每个入队必须等待一个出队线程池newCachedThreadPoolPriorityBlockingQueue堆无界支持优先级排序需要按优先级处理任务的场景5.3 有界队列与无界队列的权衡无界队列的风险生产者速度远快于消费者时任务会无限堆积最终导致OutOfMemoryError在线程池中使用无界队列时线程池永远不会创建超过核心线程数的线程任务响应时间会越来越长有界队列的优势可以控制系统的资源使用避免内存溢出当队列满时可以触发线程池扩容或执行拒绝策略保证系统的可控性最佳实践生产环境中必须使用有界队列并根据业务场景合理设置队列容量。5.4 代码示例阻塞队列实现生产者消费者importjava.util.concurrent.ArrayBlockingQueue;importjava.util.concurrent.BlockingQueue;publicclassBlockingQueueDemo{privatestaticfinalBlockingQueueIntegerqueuenewArrayBlockingQueue(5);publicstaticvoidmain(String[]args){// 启动2个生产者线程for(inti0;i2;i){newThread(newProducer(),Producer-i).start();}// 启动3个消费者线程for(inti0;i3;i){newThread(newConsumer(),Consumer-i).start();}}staticclassProducerimplementsRunnable{Overridepublicvoidrun(){intnum0;while(true){try{queue.put(num);// 队列满时阻塞System.out.println(Thread.currentThread().getName()生产num);num;Thread.sleep(500);}catch(InterruptedExceptione){Thread.currentThread().interrupt();break;}}}}staticclassConsumerimplementsRunnable{Overridepublicvoidrun(){while(true){try{intnumqueue.take();// 队列空时阻塞System.out.println(Thread.currentThread().getName()消费num);Thread.sleep(1000);}catch(InterruptedExceptione){Thread.currentThread().interrupt();break;}}}}}六、ForkJoin框架分而治之的并行计算利器6.1 核心思想ForkJoin框架是Java 7引入的并行计算框架基于分而治之的思想Fork将一个大任务递归拆分成多个足够小的子任务Join并行执行所有子任务然后合并子任务的结果得到最终结果6.2 工作窃取算法Work-StealingForkJoin框架的高效性得益于工作窃取算法每个工作线程都有自己的双端队列用于存储分配给自己的任务当一个线程完成自己队列中的所有任务后会从其他线程队列的尾部窃取任务执行这种方式可以有效减少线程竞争提高CPU利用率6.3 代码示例计算1到n的和importjava.util.concurrent.ForkJoinPool;importjava.util.concurrent.RecursiveTask;publicclassForkJoinDemoextendsRecursiveTaskLong{privatestaticfinallongTHRESHOLD1000;// 任务拆分阈值privatefinallongstart;privatefinallongend;publicForkJoinDemo(longstart,longend){this.startstart;this.endend;}OverrideprotectedLongcompute(){// 如果任务足够小直接计算if(end-startTHRESHOLD){longsum0;for(longistart;iend;i){sumi;}returnsum;}// 否则拆分成两个子任务longmid(startend)/2;ForkJoinDemoleftTasknewForkJoinDemo(start,mid);ForkJoinDemorightTasknewForkJoinDemo(mid1,end);// 执行子任务leftTask.fork();rightTask.fork();// 合并子任务结果returnleftTask.join()rightTask.join();}publicstaticvoidmain(String[]args){ForkJoinPoolpoolnewForkJoinPool();longresultpool.invoke(newForkJoinDemo(1,1000000));System.out.println(计算结果result);}}6.4 适用场景与限制适用场景CPU密集型任务尤其是递归分治类任务如排序、矩阵运算、大数据处理不适用场景IO密集型任务线程会阻塞无法充分利用CPU注意事项子任务中不能执行阻塞操作否则会导致工作线程无法执行其他任务七、原子类与CAS机制无锁线程安全的实现7.1 原子类分类java.util.concurrent.atomic包提供了多种原子类用于实现无锁的线程安全操作基本类型AtomicInteger、AtomicLong、AtomicBoolean引用类型AtomicReference、AtomicStampedReference、AtomicMarkableReference数组类型AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray字段更新器AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater7.2 CAS机制详解CASCompare-And-Swap比较并交换是一种CPU原语是实现原子类的基础它包含三个操作数内存地址V、旧的预期值A、新值B当且仅当内存地址V中的值等于预期值A时将V的值更新为B整个操作是原子的不会被其他线程中断AtomicInteger的incrementAndGet()方法内部就是一个典型的CAS自旋publicfinalintincrementAndGet(){returnunsafe.getAndAddInt(this,valueOffset,1)1;}// Unsafe类中的getAndAddInt方法publicfinalintgetAndAddInt(Objecto,longoffset,intdelta){intv;do{vgetIntVolatile(o,offset);// 读取当前值}while(!compareAndSwapInt(o,offset,v,vdelta));// CAS尝试更新returnv;}7.3 CAS的三大问题ABA问题一个值从A变为B又变回ACAS会认为没有变化但实际上已经发生了变化解决方案使用AtomicStampedReference带版本号或AtomicMarkableReference带标记位自旋开销大高并发下CAS会频繁失败导致大量自旋重试浪费CPU资源解决方案高竞争场景下使用锁替代只能保证单个变量的原子性无法同时保证多个变量的原子操作解决方案使用锁或将多个变量封装成一个对象使用AtomicReference7.4 ABA问题代码示例importjava.util.concurrent.atomic.AtomicStampedReference;publicclassABADemo{privatestaticfinalAtomicStampedReferenceIntegerrefnewAtomicStampedReference(100,0);publicstaticvoidmain(String[]args)throwsInterruptedException{// 线程1执行ABA操作Threadt1newThread(()-{intstampref.getStamp();System.out.println(线程1初始版本号stamp);ref.compareAndSet(100,101,stamp,stamp1);ref.compareAndSet(101,100,ref.getStamp(),ref.getStamp()1);});// 线程2尝试更新Threadt2newThread(()-{intstampref.getStamp();System.out.println(线程2初始版本号stamp);try{Thread.sleep(1000);// 等待线程1完成ABA操作}catch(InterruptedExceptione){e.printStackTrace();}booleansuccessref.compareAndSet(100,200,stamp,stamp1);System.out.println(线程2更新是否成功success);System.out.println(当前版本号ref.getStamp());});t1.start();t2.start();t1.join();t2.join();}}执行结果线程1初始版本号0 线程2初始版本号0 线程2更新是否成功false 当前版本号2八、同步工具类CountDownLatch与CyclicBarrier8.1 CountDownLatch一次性计数器作用允许一个或多个线程等待其他线程完成一组操作原理通过一个计数器实现初始值为需要等待的线程数核心方法countDown()计数器减1await()等待计数器变为0特点一次性使用计数器变为0后无法重置8.2 CyclicBarrier循环屏障作用允许一组线程互相等待直到所有线程都到达屏障点原理通过一个计数器实现初始值为参与等待的线程数核心方法await()线程到达屏障点等待其他线程reset()重置屏障可以重复使用特点可循环使用支持在所有线程到达时执行一个额外的Runnable任务8.3 核心区别对比表特性CountDownLatchCyclicBarrier等待关系主线程等待子线程子线程互相等待计数器只能减少一次性使用可以重置循环使用额外任务不支持支持在所有线程到达时执行适用场景等待多个任务完成后汇总结果并行迭代计算多线程同步执行8.4 CyclicBarrier代码示例模拟运动员赛跑importjava.util.concurrent.CyclicBarrier;publicclassCyclicBarrierDemo{publicstaticvoidmain(String[]args){intrunnerCount5;// 创建CyclicBarrier当所有运动员到达后执行发令枪任务CyclicBarrierbarriernewCyclicBarrier(runnerCount,()-{System.out.println(所有运动员准备就绪发令枪响);});for(inti0;irunnerCount;i){newThread(newRunner(barrier),运动员(i1)).start();}}staticclassRunnerimplementsRunnable{privatefinalCyclicBarrierbarrier;publicRunner(CyclicBarrierbarrier){this.barrierbarrier;}Overridepublicvoidrun(){try{System.out.println(Thread.currentThread().getName()正在准备...);Thread.sleep((long)(Math.random()*1000));// 模拟准备时间System.out.println(Thread.currentThread().getName()准备完毕);barrier.await();// 等待其他运动员System.out.println(Thread.currentThread().getName()开始跑步);}catch(Exceptione){e.printStackTrace();}}}}总结本文总结了Java并发容器和框架中最核心的面试知识点涵盖了并发容器、阻塞队列、ForkJoin框架、原子类与CAS、同步工具类等多个方面。在面试中不仅要记住这些概念更要理解其底层原理和适用场景并能结合代码示例进行说明。