JUC总结 📅 2026/7/15 16:09:08 java.util.concurrentJUC底层核心AQS1. AQS是什么2. AQS核心思想3. AQS核心三要素4. AQS线程阻塞底层工具LockSupport5. AQS面试高频问题并发工具三巨头1. CyclicBarrier 循环屏障2. Semaphore 限流3. CountDownLatch 减法计数器并发容器1. ConcurrentHashMap重点2. CopyOnWriteArrayListCAS1. CAS是什么2. CAS原理3. CAS三大问题原子操作类 java.util.concurrent.atomic1. 基础原子类JUC底层核心AQS1. AQS是什么AbstractQueuedSynchronizer抽象队列同步器底层实现volatile修饰的state 状态 双向链表CLH阻塞队列 独占/共享锁模式--state 是AQS核心同步状态变量privatevolatileintstate;AQS基于CLH同步双向队列用volatile修饰共享变量state请求线程通过CAS去改变状态修改成功则获取锁成功获取失败则将请求线程阻塞并封装成节点加入等待队列等待被唤醒原始 CLH单向链表每个节点只存前驱指针prev只向前看只有自旋逻辑线程不会 park 阻塞、不支持中断取消。设计目标简单公平自旋锁线程只监控前一个节点状态。AQS 改造的CLH双向链表Node 同时有prev前驱指针next后继指针新增阻塞 park、中断、超时、取消排队等复杂能力单向链表完全扛不住。2. AQS核心思想如果共享资源空闲则将当前请求资源的线程设为有效工作线程并锁定共享资源。如果共享资源被占用则需要一套线程阻塞排队和唤醒机制来分配锁。3. AQS核心三要素state同步状态0 无锁0 持有锁可自定义含义CountDownLatch 用 state 做计数器双向阻塞队列等待线程封装成 Node轻量自旋 阻塞两种模式独占锁ReentrantLock、共享锁Semaphore、CountDownLatch、ReadWriteLockReentrantLock 可重入独占锁可重入同一线程多次加锁sate累加支持公平锁/非公平锁new ReentrantLock(true/false)尝试获取锁超时tryLock(时间, 单位)4. AQS线程阻塞底层工具LockSupport所有锁阻塞线程底层都是 LockSupportpark()阻塞线程unpark(Thread)唤醒与 wait/notify 区别不需要先获取锁、可先唤醒再阻塞、精准唤醒指定线程5. AQS面试高频问题AQS 排队线程为什么用自旋而不是直接阻塞AQS 排队线程先用自旋而不直接阻塞本质是性能权衡绝大多数锁临界区执行极快短暂自旋可以规避昂贵的操作系统线程阻塞 / 唤醒上下文切换只有确定短时间拿不到锁时才会调用 park 进入内核阻塞。AQS的CLH队列为什么是双向链表原始CLH是单向链表仅简单自旋支持线程中途取消cancelAcquire中间节点可支持 O (1) 删除node.prev.next node.next;node.next.prev node.prev;AQS的核心变量state为什么要用volatile修饰① 保证多线程可见性② 禁止JVM指令重排序保证锁状态值修改有序③ 为 CAS 提供最新内存值保证同步逻辑正确公平锁/非公平锁在AQS里的区别实现?底层是 AQS 两个子类差异仅在 tryAcquire非公平state 为 0 时直接 CAS 抢占无视等待队列公平state 为 0 时先执行 hasQueuedPredecessors队列有线程则放弃抢占必须排队并发工具三巨头1. CyclicBarrier 循环屏障可重复使用的屏障当请求线程达到一定的数量才会放行放行之后会再次重置待拦截的线程数量代码示例publicstaticvoidtestCyclicBarrier(){CyclicBarriercyclicBarriernewCyclicBarrier(4,()-System.out.println(Thread.currentThread().getName()人满开始游戏));classTaskimplementsRunnable{privateCyclicBarriercyclicBarrier;publicTask(CyclicBarriercyclicBarrier){this.cyclicBarriercyclicBarrier;}Overridepublicvoidrun(){try{System.out.println(Thread.currentThread().getName()准备就绪);cyclicBarrier.await();// 一进来就被阻塞并加入等待队列中System.out.println(Thread.currentThread().getName()游戏中...);}catch(InterruptedException|BrokenBarrierExceptione){e.printStackTrace();}}}for(inti0;i8;i){try{TimeUnit.MILLISECONDS.sleep(500);// 增加间隔时间让效果更加直观清晰newThread(newTask(cyclicBarrier),线程_i).start();}catch(InterruptedExceptione){e.printStackTrace();}}}控制台输出线程_0准备就绪 线程_1准备就绪 线程_2准备就绪 线程_3准备就绪 线程_3人满开始游戏 线程_3游戏中... 线程_0游戏中... 线程_1游戏中... 线程_2游戏中... 线程_4准备就绪 线程_5准备就绪 线程_6准备就绪 线程_7准备就绪 线程_7人满开始游戏 线程_7游戏中... 线程_4游戏中... 线程_5游戏中... 线程_6游戏中...2. Semaphore 限流Semaphore 就是一个信号量它的作用是单机应用限流限制某个接口的并发数Semaphore的剩余许可量是通过AQS中的state属性进行的记录获取许可是将该值进行减少释放许可是将该值进行增加当没有足够的许可时线程会加入到阻塞队列中等待其他线程释放许可并唤醒。代码示例publicstaticvoidtestSemaphore(){SemaphoresemaphorenewSemaphore(3);classRUNimplementsRunnable{privateSemaphoresemaphore;publicRUN(Semaphoresemaphore){this.semaphoresemaphore;}Overridepublicvoidrun(){StringthreadNameThread.currentThread().getName();try{System.out.println(剩余许可证semaphore.availablePermits());semaphore.acquire();// 许可不足的话当前线程就会被阻塞并且加入等待队列直到有一个可用的许可证为止,就会被唤醒System.out.println(threadName获取到许可证执行任务);TimeUnit.SECONDS.sleep(2);}catch(InterruptedExceptione){e.printStackTrace();}finally{System.out.println(threadName完成任务释放许可证);semaphore.release();}}}for(inti0;i12;i){newThread(newRUN(semaphore),线程i).start();}}控制台输出剩余许可证3 线程0获取到许可证执行任务 剩余许可证2 线程3获取到许可证执行任务 剩余许可证1 线程4获取到许可证执行任务 剩余许可证0 剩余许可证0 剩余许可证0 剩余许可证0 剩余许可证0 剩余许可证0 剩余许可证0 剩余许可证0 剩余许可证0 线程3完成任务释放许可证 线程0完成任务释放许可证 线程7获取到许可证执行任务 线程8获取到许可证执行任务 线程4完成任务释放许可证 线程11获取到许可证执行任务 线程7完成任务释放许可证 线程11完成任务释放许可证 线程1获取到许可证执行任务 线程8完成任务释放许可证 线程2获取到许可证执行任务 线程5获取到许可证执行任务 线程2完成任务释放许可证 线程1完成任务释放许可证 线程5完成任务释放许可证 线程9获取到许可证执行任务 线程6获取到许可证执行任务 线程10获取到许可证执行任务 线程6完成任务释放许可证 线程9完成任务释放许可证 线程10完成任务释放许可证3. CountDownLatch 减法计数器CountDownLatch 翻译过来就是倒数的门锁可以理解为 计数器屏障CountDownLatch 的使用场景一主线程等待多个子线程完成任务后进行汇总合并。// 主线程等待多个子线程(任务)完成后进行汇总合并publicstaticvoidtestCountDownLatch()throwsInterruptedException{longstartSystem.currentTimeMillis();CountDownLatchcountDownLatchnewCountDownLatch(5);classRUNimplementsRunnable{privateCountDownLatchlatch;publicRUN(CountDownLatchlatch){this.latchlatch;}Overridepublicvoidrun(){try{System.out.println(Thread.currentThread().getName()_开始执行);try{TimeUnit.SECONDS.sleep(2);}catch(InterruptedExceptione){//}}finally{System.out.println(Thread.currentThread().getName()_执行完毕准备对计数器进行 减1操作);latch.countDown();// 使用CAS 对 AQS的volatile state属性进行-1操作}}}for(inti0;i5;i){newThread(newRUN(countDownLatch)).start();}System.out.println(...主线程准备等待...);// 主线程等待当计数器state0就唤醒主线程往下执行可设置最大等待时间。countDownLatch.await();System.out.println(...主线程完成等待...);System.out.println(总花费时间__(System.currentTimeMillis()-start));}...主线程准备等待...Thread-0_开始执行Thread-2_开始执行Thread-3_开始执行Thread-1_开始执行Thread-4_开始执行Thread-0_执行完毕准备对计数器进行 减1操作Thread-1_执行完毕准备对计数器进行 减1操作Thread-4_执行完毕准备对计数器进行 减1操作Thread-3_执行完毕准备对计数器进行 减1操作Thread-2_执行完毕准备对计数器进行 减1操作...主线程完成等待...总花费时间__2030CountDownLatch 的使用场景二多个线程等待模拟并发请求一进来就被阻塞让线程一起执行// 多个线程等待模拟并发请求一进来就被阻塞让并发线程一起执行publicstaticvoidtestCountDownLatch2()throwsInterruptedException{CountDownLatchcountDownLatchnewCountDownLatch(1);classRUNimplementsRunnable{privateCountDownLatchlatch;publicRUN(CountDownLatchlatch){this.latchlatch;}Overridepublicvoidrun(){try{System.out.println(Thread.currentThread().getName()_阻塞等待);latch.await();//所有请求都阻塞在这等待System.out.println(Thread.currentThread().getName()_开始执行);TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()_执行完毕);}catch(Exceptione){e.printStackTrace();}}}for(inti0;i4;i){newThread(newRUN(countDownLatch),线程i).start();}TimeUnit.SECONDS.sleep(5);// 主线程休眠 让 子线程请求都准备好System.out.println(MAIN 线程 使得计数减1... 唤醒所有子线程...);countDownLatch.countDown();// 主线程休眠后 调用countDown() 是计数器减1 所有的子线程将会被放行}线程2_阻塞等待 线程3_阻塞等待 线程0_阻塞等待 线程1_阻塞等待MAIN线程 使得计数减1...唤醒所有子线程...线程2_开始执行 线程1_开始执行 线程0_开始执行 线程3_开始执行 线程1_执行完毕 线程2_执行完毕 线程0_执行完毕 线程3_执行完毕并发容器1. ConcurrentHashMap重点2. CopyOnWriteArrayList原理写时复制加锁复制新数组替换原数组读无锁优缺点读极快、写性能差适合读多写少场景内存占用高、弱一致性CAS1. CAS是什么CASCompare And Swap比较和交换是实现无锁乐观并发的底层原子操作。2. CAS原理cas(V, A, B)V内存中实际变量值主存值A预期值线程本地读取到的旧值B要修改的新值执行逻辑原子不可分割整个「比较 赋值」是单条 CPU 指令中途不会被线程切换打断天然原子1. 比较判断 V A 2. 两种结果 - 相等说明期间没人修改过把内存 V 更新为 B返回 true修改成功 - 不相等说明其他线程改动过数据放弃更新返回 false修改失败3. CAS三大问题1. ABA 问题CAS 只对比数值不关心中间修改记录。线程读取 A其他线程改成 B 再改回 A当前线程 CAS 会误认为没修改更新成功造成数据错误。解决增加版本戳 AtomicStampedReference。给变量附加自增版本戳 stampCAS 同时比较**值版本号**每次修改版本号1。-初始(A,stamp1)-B 修改(B,stamp2) → (A,stamp3)-A 比较时预期 stamp1实际 stamp3CAS 失败识别 ABA。2. 自旋空耗 CPU高并发大量线程同时竞争CAS 频繁失败无限循环占用 CPUCPU 使用率飙升。解决自适应自旋、LongAdder 分段、竞争激烈降级为互斥锁。3. 仅支持单个变量原子更新硬件指令只能操作一个内存地址无法一次性原子修改多个关联变量。解决多字段封装为对象CAS 替换整个对象引用或使用锁。原子操作类 java.util.concurrent.atomic基于 CAS volatile无锁并发性能优于同步锁1. 基础原子类AtomicInteger、AtomicLong、AtomicBoolean核心原理CAS 自旋乐观锁