在Java后端面试中并发编程始终是大厂考察的核心重点——无论是校招的基础问答还是社招的底层原理深挖并发相关知识点都能直接拉开面试差距。本文整理了23道最常考的Java并发面试题涵盖内存模型、锁机制、线程池、AQS等核心模块每道题均包含「标准回答深度扩展」扩展内容贴合面试官高频追问方向吃透这一篇轻松应对并发面试难点。注题目后√表示基础必背×表示难点深挖建议优先掌握必背题再攻克难点题。一、基础核心必背√1. Java的内存模型介绍一下标准回答JMMJava Memory ModelJava内存模型本质是一套规范用于解决多线程环境下并发访问共享数据导致的错误核心解决原子性、有序性、可见性三大问题。JMM定义了主内存和工作内存两大区域所有共享变量实例变量、静态变量存储在主内存每个线程有独立的工作内存线程操作数据时需先从主内存拷贝数据到工作内存操作完成后再同步回主内存。volatile关键字是JMM的核心工具可控制数据加载/写入时机禁止不合理指令重排避免多线程读取数据出错。扩展知识点1. 三大核心问题详解面试必答- 原子性一个操作或一组操作要么全部执行且不被中断要么全部不执行例i不是原子操作需synchronized或原子类保证而volatile无法保证原子性- 可见性一个线程修改共享变量后其他线程能立即看到修改后的值volatile通过强制刷新主内存实现synchronized通过释放锁时同步主内存实现final修饰的变量也具备可见性- 有序性程序执行顺序需符合代码逻辑编译器、处理器为优化性能会进行指令重排但重排需满足“不影响单线程执行结果”和“不重排有数据依赖的指令”两个条件volatile可禁止指令重排synchronized、锁机制也能间接保证有序性。2. 主内存与工作内存的交互规则JMM定义8种操作核心4种- read读取将主内存的共享变量读取到工作内存- load加载将read读取的数据存入工作内存的变量副本- store存储将工作内存中修改后的变量副本存入主内存- write写入将store存储的数据更新到主内存的共享变量中。3. 易混淆点JMM与JVM内存区域的区别——JMM是“规范”关注多线程数据一致性JVM内存区域堆、栈、方法区是“实际存储结构”关注数据物理存储位置二者无直接对应关系但JMM的规范依赖JVM内存区域实现。2. 保证数据一致性的方案有哪些标准回答保证数据一致性尤其是多线程、多数据源场景核心有3类方案覆盖不同应用场景1. 事务管理依赖数据库的ACID特性原子性、一致性、隔离性、持久性确保单个或多个数据库操作要么全部成功要么全部回滚避免数据部分修改导致的不一致2. 锁机制使用synchronized关键字、ReentrantLock等锁保证同一时刻只有一个线程能操作共享数据避免并发修改导致的数据错乱3. 版本控制乐观锁在数据中添加版本号/时间戳更新数据时判断当前版本与数据库版本是否一致一致则更新版本号1不一致则拒绝更新避免并发修改冲突。扩展知识点1. 事务管理补充除了数据库原生事务Java中可通过Spring的声明式事务Transactional简化开发需注意两个关键- 事务传播机制如REQUIRED默认当前无事务则新建有则加入、REQUIRES_NEW无论当前有无事务均新建事务- 事务失效场景非public方法、异常被捕获未抛出、手动提交/回滚事务、数据源未配置事务管理器等。2. 锁机制细分除了单机锁synchronized、ReentrantLock分布式场景需使用分布式锁Redis分布式锁、ZooKeeper分布式锁此外读写锁ReentrantReadWriteLock可兼顾读多写少场景的性能允许多个线程同时读单个线程写。3. 乐观锁的3种实现方式- 版本号机制数据库表添加version字段更新SQLupdate table set ... where id? and version?更新成功后version自增- 时间戳机制用timestamp字段替代version通过时间戳判断数据是否被修改原理与版本号一致- CAS机制Java原子类AtomicInteger、AtomicReference底层实现无需加锁通过比较并替换实现无锁并发保证单个变量原子性。4. 场景选择单线程场景无需额外处理单节点多线程用单机锁/原子类数据库操作用事务分布式场景用分布式锁事务。3. 线程的创建方式有哪些标准回答Java中线程的创建有4种核心方式各有优缺点适配不同场景1. 继承Thread类直接继承Thread重写run()方法调用start()方法启动线程。优点代码简单直观缺点Java单继承继承Thread后无法继承其他类灵活性差。2. 实现Runnable接口实现Runnable接口重写run()方法将Runnable实例传入Thread构造器调用start()启动。优点可继承其他类灵活性高缺点代码略复杂无法直接获取返回值。3. 实现Callable接口FutureTask实现Callable接口重写call()方法有返回值、可抛出异常将Callable实例包装成FutureTask再传入Thread构造器启动。优点有返回值、可处理异常缺点代码最复杂。4. 使用线程池通过Executors工具类或ThreadPoolExecutor手动创建线程池从线程池中获取线程执行任务。优点复用线程避免频繁创建/销毁线程的性能开销控制并发数缺点需手动配置参数否则易出现性能问题。扩展知识点1. 核心区别对比- 继承Thread vs 实现Runnable核心是“单继承限制”Runnable更推荐遵循“单一职责原则”将线程任务与线程本身分离- Runnable vs Callable核心是“返回值和异常”Runnable的run()无返回值、不能抛checked异常Callable的call()有返回值、可抛checked异常- 前3种方式 vs 线程池前3种每次创建新线程执行完销毁适合少量、临时任务线程池适合频繁执行的任务如接口请求、定时任务提升性能。2. 关键注意点- 启动线程必须调用start()方法不能直接调用run()——start()会触发JVM创建新线程执行run()直接调用run()只是普通方法调用不会创建新线程- FutureTask的作用不仅能包装Callable还能包装Runnable可通过get()方法获取任务结果会阻塞当前线程通过cancel()方法取消任务- 线程池推荐创建方式不推荐Executors工具类如newCachedThreadPool无上限创建线程易OOM推荐ThreadPoolExecutor手动创建灵活配置核心参数核心线程数、最大线程数、队列容量等。4. Java线程的状态有哪些标准回答根据Java官方文档线程有6种状态状态之间可相互转换但有明确规则具体如下1. NEW新建状态线程刚被创建如new Thread()未调用start()方法不占用CPU资源2. RUNNABLE可运行状态调用start()后进入此状态分为“就绪态”等待CPU调度和“运行态”正在执行run()方法3. BLOCKED阻塞状态线程等待监视器锁synchronized锁释放如线程A持有锁线程B尝试获取同一把锁线程B进入BLOCKED状态4. WAITING无限等待状态线程调用wait()方法无超时时间需其他线程调用notify()/notifyAll()唤醒否则一直等待5. TIMED_WAITING计时等待状态线程调用sleep(long)、wait(long)、join(long)等方法有超时时间超时后自动唤醒也可被提前唤醒6. TERMINATED终止状态线程执行完run()方法或因异常终止生命周期结束无法恢复。扩展知识点1. 核心状态转换路径必背- NEW → RUNNABLE调用start()方法- RUNNABLE → BLOCKED尝试获取synchronized锁失败- RUNNABLE → WAITING调用wait()无超时- RUNNABLE → TIMED_WAITING调用sleep(long)、wait(long)等- BLOCKED/WAITING/TIMED_WAITING → RUNNABLE获取锁、被唤醒、超时- RUNNABLE → TERMINATED任务完成或异常终止。2. 易混淆状态区分- BLOCKED vs WAITINGBLOCKED是“等待锁”WAITING是“等待唤醒”等待原因不同- WAITING vs TIMED_WAITING核心是“是否有超时时间”TIMED_WAITING不会一直等待- RUNNABLE的两个子状态Java API不区分就绪态和运行态统一称为RUNNABLECPU调度决定线程从就绪态转为运行态。3. 线程终止的正确方式不推荐stop()方法强制终止线程可能导致资源泄露推荐用“标志位”控制如定义volatile boolean flag线程循环判断flag外部修改flag让线程退出。5. sleep和wait的区别是什么标准回答sleep和wait都是让线程进入等待状态的方法核心区别有3点1. 所属类型不同sleep是Thread类的静态方法可直接通过Thread.sleep()调用wait是Object类的实例方法需通过锁对象调用如obj.wait()2. 使用位置不同sleep可在任何位置使用无需加锁wait只能在synchronized同步块或同步方法中使用必须持有锁对象3. 唤醒机制与锁释放不同sleep会在指定时间后自动唤醒期间不会释放持有的锁wait需其他线程调用同一锁对象的notify()/notifyAll()唤醒期间会释放持有的锁。扩展知识点1. 补充核心区别面试加分- 等待状态不同sleep使线程进入TIMED_WAITING状态wait无超时使线程进入WAITING状态wait有超时使线程进入TIMED_WAITING状态- 异常处理不同二者均抛出InterruptedException异常需强制捕获- 调用场景不同sleep用于“暂停线程执行”如模拟网络延迟wait用于“线程间通信”如生产者-消费者模型。2. 经典场景示例- 生产者-消费者模型生产者生产数据后调用obj.wait()释放锁让消费者获取锁消费消费者消费完成后调用obj.notify()唤醒生产者实现线程协作。3. 易错点- 调用wait()的线程必须持有锁对象否则抛出IllegalMonitorStateException异常- sleep(0)看似暂停0毫秒实际是让当前线程放弃CPU调度重新进入就绪态让其他线程有机会执行- 线程被唤醒后不会立即执行需重新等待CPU调度进入就绪态。6. notify和notifyall的区别标准回答notify和notifyAll都是Object类的方法用于唤醒处于wait状态的线程核心区别在于“唤醒的线程数量”1. notify()随机唤醒等待同一锁对象的一个线程被唤醒的线程进入就绪态参与锁竞争其余等待线程仍处于WAITING状态若被唤醒的线程未释放锁或未调用notify()其余线程会一直等待或被中断2. notifyAll()唤醒等待同一锁对象的所有线程所有被唤醒的线程进入就绪态共同竞争锁抢到锁的线程执行执行完毕释放锁后其余线程继续竞争直到所有线程执行完成。扩展知识点1. 底层原理等待同一锁对象的线程会被放入该锁的“等待队列”notify()从队列中随机选一个线程唤醒notifyAll()唤醒队列中所有线程清空等待队列。2. 场景选择- notify()适合“只有一个线程能处理任务”的场景如单个消费者避免多线程竞争提升性能- notifyAll()适合“多个线程能处理任务”的场景如多个消费者避免因notify()随机唤醒导致部分线程长期等待线程饥饿。3. 注意点- 二者均需在synchronized同步块/方法中调用否则抛出IllegalMonitorStateException异常- 被唤醒的线程不会立即获取锁需重新竞争锁因wait()期间释放了锁- 若没有线程处于wait状态调用二者不会有任何效果也不会报错。二、难点深挖× 核心进阶√7. Java中有哪些常用的锁在什么场景下使用×标准回答Java中的常用锁按“使用方式”和“特性”可分为5大类各有明确适配场景核心如下1. 内置锁synchronizedJava原生隐式锁通过synchronized关键字实现进入同步块/方法自动获取锁退出自动释放锁。适用于简单同步场景如单线程修改共享变量易用性高无需手动管理锁。2. 显式锁ReentrantLockjava.util.concurrent.locks包下的显式锁需手动调用lock()获取锁、unlock()释放锁通常在finally块中释放。适用于复杂同步场景如需要公平锁、响应中断灵活性高。3. 读写锁ReentrantReadWriteLock显式锁的一种分为读锁共享锁和写锁独占锁允许多个线程同时读单个线程写。适用于读多写少场景如缓存查询、数据统计兼顾读性能和写安全性。4. 乐观锁无锁机制不主动加锁通过版本号、CAS等方式保证数据一致性。适用于并发冲突少、读多写少场景如电商库存更新性能高于悲观锁。5. 悲观锁主动加锁认为并发冲突一定会发生操作数据前先获取锁。适用于并发冲突多、写多读少场景如银行转账安全性高。扩展知识点1. 各锁补充说明- 内置锁synchronizedJVM层面实现JDK1.8后经过偏向锁、轻量级锁、重量级锁升级性能接近ReentrantLock无需手动释放锁避免锁泄露- ReentrantLock支持公平/非公平锁、响应中断、超时获取锁tryLock(long, TimeUnit)、条件变量Condition可实现复杂同步逻辑如多条件等待- ReentrantReadWriteLock读锁可共享写锁独占读锁和写锁可重入写锁获取后其他线程无法获取读锁和写锁避免读写冲突- 乐观锁除了版本号、CAS还有时间戳机制无需加锁减少线程阻塞开销但需处理并发冲突如重试机制- 悲观锁除了单机锁还有数据库行锁、表锁分布式场景用Redis/ZK分布式锁。2. 场景选择总结面试必答- 简单同步、无需复杂功能 → synchronized- 复杂同步、需要公平锁/中断/超时 → ReentrantLock- 读多写少、兼顾读性能 → ReentrantReadWriteLock- 并发冲突少、读多写少 → 乐观锁- 并发冲突多、写多读少 → 悲观锁- 分布式场景 → 分布式锁。8. Java的并发工具都有哪些√标准回答Java并发工具主要集中在java.util.concurrent包下核心有3类面试高频用于解决多线程协作、资源控制问题1. 倒计时门闩CountDownLatch允许一个或多个线程等待其他线程完成指定数量的操作后再继续执行。核心是“计数器递减”初始化计数器为N其他线程完成任务后调用countDown()减1等待线程调用await()阻塞直到计数器为0被唤醒。2. 循环屏障CyclicBarrier允许一组线程互相等待直到所有线程都到达一个公共屏障点之后所有线程同时继续执行。核心是“等待所有线程就绪”可重复使用计数器可重置。3. 信号量Semaphore用于控制同时访问某个共享资源的线程数量核心是“许可机制”初始化许可数为N线程获取许可acquire()后才能访问资源访问完成后释放许可release()许可不足时线程阻塞。扩展知识点1. 补充其他常用并发工具- 线程池ThreadPoolExecutor最常用复用线程、控制并发数管理任务生命周期- 阻塞队列BlockingQueue线程间通信工具存储任务/数据支持阻塞式添加/移除如ArrayBlockingQueue、LinkedBlockingQueue是线程池核心组件- 原子类AtomicInteger、AtomicLong等无锁并发工具基于CAS实现保证单个变量原子性- Condition与ReentrantLock配合实现多条件等待/唤醒比wait()/notify()更灵活。2. 核心工具场景对比高频追问- CountDownLatch一次性使用适合“一个线程等待多个线程”如主线程汇总子线程结果或“多个线程等待一个线程”如子线程等待主线程初始化- CyclicBarrier可重复使用适合“多个线程协同完成任务”如多线程分块处理数据所有线程处理完后汇总- Semaphore适合“控制共享资源并发访问数”如控制同时访问数据库的线程数避免资源过载。3. 易混淆点CountDownLatch vs CyclicBarrier- 复用性CountDownLatch一次性计数器为0后无法重置CyclicBarrier可通过reset()重置- 等待机制CountDownLatch是“等待其他线程完成”角色固定CyclicBarrier是“所有线程互相等待”角色一致- 触发动作CyclicBarrier可设置Runnable参数所有线程到达屏障点时先执行该Runnable再继续执行。9. CountDownLatch是做什么的×标准回答CountDownLatch倒计时门闩是Java并发工具核心作用是“线程间同步等待”允许一个或多个线程等待线程等待其他N个线程执行线程完成指定操作后再继续执行自己的任务无法重复使用。核心工作流程4步1. 初始化计数器创建CountDownLatch实例时指定计数器初始值NN等于需要等待的线程数或任务数2. 等待线程阻塞等待线程调用await()方法进入阻塞状态直到计数器值变为03. 执行线程完成通知每个执行线程完成任务后调用countDown()方法将计数器值减14. 唤醒等待线程当计数器值减到0时所有处于await()阻塞状态的等待线程会被同时唤醒继续执行后续逻辑。扩展知识点1. 核心方法详解- CountDownLatch(int count)构造方法count≥0count0时await()不会阻塞- await()等待线程调用阻塞当前线程直到计数器为0被中断会抛出InterruptedException- await(long timeout, TimeUnit unit)带超时时间的等待超时后计数器仍不为0则自动唤醒返回false- countDown()执行线程调用计数器原子性减1计数器为0时唤醒所有等待线程- getCount()获取当前计数器值用于调试或判断任务进度。2. 经典使用场景- 场景1主线程等待所有子线程完成。如主线程汇总多个子线程的执行结果先启动子线程主线程调用await()阻塞子线程执行完调用countDown()- 场景2多个子线程等待主线程初始化。如所有子线程需要使用主线程初始化的资源主线程初始化完成后调用countDown()子线程调用await()阻塞- 场景3等待多个任务完成。如执行3个耗时任务初始化CountDownLatch(3)每个任务完成后countDown()等待线程await()。3. 注意点- 一次性使用计数器为0后再调用countDown()无效await()不再阻塞- 避免死等若执行线程异常未调用countDown()计数器一直不为0等待线程会一直阻塞可使用带超时的await()- 底层实现基于AQS计数器本质是AQS的state变量countDown()对应AQS的release()await()对应AQS的acquire()。10. synchronized和ReentrantLock及其使用场景√标准回答synchronized和ReentrantLock都是Java中保证线程安全的锁二者核心功能一致但实现方式、功能特性、使用场景不同具体如下一、synchronized内置锁/监视器锁1. 实现原理JVM层面隐式实现基于对象头的Mark Word和管程Monitor进入同步块/方法时JVM自动添加monitorenter指令获取锁退出时添加monitorexit指令释放锁。2. 核心逻辑锁的持有通过“计数器”控制——线程获取锁时计数器加1重入时再加1释放时减1计数器为0时锁完全释放其他线程可竞争。synchronized是排他锁同一时刻只有一个线程能持有锁。3. 使用场景简单同步场景无需复杂锁功能适合初学者易用性高无需手动管理锁释放。二、ReentrantLock显式锁1. 实现原理Java代码层面显式实现底层基于AQS抽象队列同步器通过AQS的state变量记录锁状态双向等待队列管理竞争锁的线程支持可重入、公平/非公平锁。2. 核心逻辑4个维度- 核心依赖AQS的state变量0未持有0已持有值为重入次数和双向同步队列- 可重入实现首次加锁CAS修改state为1记录锁持有者重入时state加1解锁时state减1state0时释放锁- 加解锁流程加锁时先尝试CAS抢锁失败则检查是否重入否则加入等待队列解锁时state减1state0时唤醒队列头节点- 公平/非公平锁默认非公平锁抢锁优先性能高公平锁按队列顺序抢锁无饥饿通过构造器指定new ReentrantLock(true)。3. 使用场景复杂同步场景需要公平锁、响应中断、超时获取锁、多条件等待等功能适合高并发、复杂业务逻辑。扩展知识点1. ReentrantLock的额外功能面试加分- 响应中断lockInterruptibly()方法获取锁时线程可被中断避免长期阻塞- 超时获取锁tryLock(long, TimeUnit)超时未获取锁返回false避免死等- 条件变量ConditionnewCondition()获取Condition对象可实现多条件等待/唤醒如分别唤醒生产者和消费者- 锁状态查询isLocked()、isHeldByCurrentThread()等方法便于调试。2. 使用注意点- ReentrantLock需在finally块中调用unlock()否则会导致锁泄露- synchronized无需手动释放锁JVM自动释放无锁泄露风险- 公平锁性能低于非公平锁因需频繁判断队列增加线程切换开销。11. synchronized和ReentrantLock的区别√标准回答synchronized和ReentrantLock的核心区别有5点面试必背1. 用法不同synchronized可用于普通方法、静态方法、同步代码块ReentrantLock只能用于同步代码块需手动调用lock()/unlock()。2. 锁的获取与释放方式不同synchronized是隐式锁JVM自动获取/释放ReentrantLock是显式锁需手动获取/释放。3. 锁类型不同synchronized只支持非公平锁ReentrantLock既支持非公平锁默认也支持公平锁。4. 响应中断不同synchronized不支持响应中断ReentrantLock支持响应中断可解决死锁问题。5. 底层实现不同synchronized是JVM层面基于管程实现ReentrantLock是Java代码层面基于AQS实现。扩展知识点1. 补充核心区别高频追问- 锁状态查询ReentrantLock可查询锁状态synchronized无法查询只能通过代码逻辑判断- 条件变量支持ReentrantLock可通过Condition实现多条件等待/唤醒synchronized只能通过wait()/notify()实现且只有一个等待队列- 性能差异JDK1.7及之前synchronized性能远低于ReentrantLockJDK1.8后synchronized经过锁升级优化性能接近ReentrantLock简单场景下更优- 锁泄露风险ReentrantLock易因忘记释放锁导致泄露synchronized无此风险。2. 易错点不要说“synchronized性能比ReentrantLock差”需说明“JDK1.8后二者性能接近简单场景synchronized更优复杂场景ReentrantLock更灵活”。12. 如何理解可重入锁√标准回答可重入锁Reentrant Lock的核心定义同一个线程在已经持有某把锁的情况下再次请求获取同一把锁时不会发生阻塞不会产生死锁能直接重入锁只有其他线程尝试获取该锁时才会被阻塞等待。可重入锁的底层实现核心依赖两个关键变量1. Owner线程标识记录当前持有锁的线程ID判断当前请求锁的线程是否是持有者2. Count重入计数器记录锁的重入次数判断锁是否被完全释放。核心工作流程3步1. 首次获取锁锁未被占用Owner为null将Owner设为当前线程IDCount设为1获取锁成功2. 重入获取锁当前线程是锁持有者Count加1直接获取锁成功否则阻塞等待3. 释放锁Count减1Count0时清空Owner标识锁完全释放其他线程可竞争。扩展知识点1. 核心意义避免死锁简化代码逻辑。如同步方法A调用同步方法B同一把锁若锁不可重入线程会自身阻塞产生死锁可重入锁则能避免。2. Java中的可重入锁实例synchronized隐式可重入、ReentrantLock显式可重入、ReentrantReadWriteLock读写锁可重入。3. 注意点- 重入前提必须是“同一把锁”不同锁无法重入- 释放次数重入几次需释放几次否则锁不会完全释放- 不解决不同线程的并发冲突只允许同一线程重复获取锁。13. synchronized支持重入吗如何实现的√标准回答支持synchronized是隐式可重入锁这是它的核心特性之一。当一个线程已经获取了某个对象的synchronized锁后再次请求同一个对象的synchronized锁时不会阻塞能直接重入。其可重入性是JVM层面隐式完成的核心依赖两个关键载体实现“持有者线程标记 重入计数器”的组合管理1. 锁对象的Mark Word存储锁状态、持有者线程ID等信息用于判断锁是否被持有、持有者是谁2. 线程栈帧中的锁记录Lock Record每个线程的栈帧中会创建锁记录存储锁对象引用、重入计数器等信息。具体实现流程3步1. 首次获取锁JVM检查锁对象的Mark Word锁未被占用时在当前线程栈帧中创建锁记录通过Mark Word标记当前线程为持有者计数器初始化为12. 重入获取锁验证当前线程是锁持有者将锁记录的计数器加1无需竞争重入成功重入前提同一对象的锁3. 释放锁线程退出同步块/方法时计数器减1计数器为0时清空Mark Word的持有者标识锁完全释放。扩展知识点1. 锁记录细节每个synchronized同步块/方法对应一个锁记录重入时计数器累加确保重入次数正确。2. 不同场景的可重入示例- 同步方法调用同步方法同一对象可重入- 同步代码块嵌套同步代码块同一对象可重入- 静态同步方法与普通同步方法不同对象类对象vs实例对象不可重入需正常竞争。3. 易错点不要混淆“同一对象的锁”和“不同对象的锁”synchronized的重入仅针对同一对象。14. synchronized锁升级的过程×标准回答synchronized的锁升级是JVM对synchronized锁的核心优化策略核心目的是减少重量级锁的性能开销重量级锁依赖管程线程阻塞/唤醒开销大。锁升级遵循无锁 → 偏向锁 → 轻量级锁 → 重量级锁的单向路径不可逆升级依据是对象头的Mark Word存储锁状态信息。具体升级过程4步1. 初始状态无锁对象刚被创建时锁状态为无锁Mark Word存储对象的哈希值、分代年龄等基础信息无锁相关标记只有线程第一次进入synchronized代码块竞争该对象的锁时才触发锁升级。2. 第一步升级偏向锁针对单线程重复获取锁的优化核心思想是“谁先拿到就偏向谁”。加锁逻辑第一个线程获取锁时JVM通过CAS操作将Mark Word的偏向锁标记设为1记录当前线程ID无需创建锁记录线程再次进入同步块时只需判断Mark Word的线程ID是否为自己无需竞争直接重入。升级触发条件有第二个线程尝试竞争该锁时偏向锁被撤销升级为轻量级锁撤销时需暂停持有锁的线程。3. 第二步升级轻量级锁适用于多线程交替获取锁的场景无激烈竞争核心依赖线程栈帧的锁记录和CAS操作。加锁逻辑线程获取锁时JVM在其栈帧中创建锁记录将Mark Word复制到锁记录再通过CAS将Mark Word替换为指向锁记录的指针CAS成功则获取锁失败则自旋尝试CAS默认10次。自旋意义让线程原地循环等待避免线程阻塞/唤醒的开销多数情况下持有锁的线程会快速释放锁。升级触发条件自旋次数达到阈值或多个线程同时自旋竞争锁轻量级锁升级为重量级锁。4. 最终状态重量级锁适用于多线程激烈竞争锁的场景核心依赖JVM底层的管程对象开销最大。加锁逻辑Mark Word替换为指向管程的指针线程获取锁时进入管程入口集获取成功则成为管程持有者失败则被阻塞放入管程等待队列直到锁被释放后唤醒。扩展知识点1. 锁升级的核心优化逻辑从“无锁”到“重量级锁”是根据线程竞争激烈程度逐步升级避免一开始就使用重量级锁导致的性能开销。2. 偏向锁的补充JDK1.8默认开启偏向锁可通过JVM参数-XX:-UseBiasedLocking关闭偏向锁撤销的开销较大因此单线程场景下偏向锁能大幅提升性能。3. 轻量级锁的补充自旋次数可通过JVM参数-XX:PreBlockSpin调整JDK1.7后引入自适应自旋根据之前的自旋成功率动态调整自旋次数。4. 各锁状态的性能对比偏向锁 轻量级锁 重量级锁竞争越激烈性能越低。15. 介绍一下AQS√标准回答AQS的全称是AbstractQueuedSynchronizer抽象队列同步器是Java并发包的核心基础框架。核心思想是“用一个volatile修饰的int型状态变量state表示同步状态结合一个FIFO双向等待队列管理竞争锁的线程”它将锁的获取、释放等核心逻辑抽象出来子类只需重写特定方法就能实现不同类型的同步器。从三个核心维度详解AQS1. AQS的核心组成同步状态变量state核心和双向等待队列存储阻塞的线程2. 两种资源共享模式- 独占模式同一时刻只能有一个线程获取锁如ReentrantLock- 共享模式同一时刻可以有多个线程获取锁如CountDownLatch、Semaphore3. 核心流程以独占模式的ReentrantLock为例- 获取锁尝试CAS修改state0→1成功则标记线程为持有者失败则判断是否重入state1否则封装成Node节点加入队列尾部挂起线程被唤醒后重新尝试获取锁- 释放锁state减1state0时清空持有者标识唤醒队列头节点线程被唤醒的线程重新竞争锁。扩展知识点1. state变量的意义state是volatile修饰的保证可见性和有序性不同同步器对state的定义不同- ReentrantLockstate表示重入次数0未持有0已持有- Semaphorestate表示可用许可数- CountDownLatchstate表示计数器值。2. 双向等待队列的细节队列是FIFO先进先出每个节点是Node对象存储线程信息、前驱/后继节点用于管理阻塞的线程队列头节点是“哨兵节点”不存储线程仅用于标记队列头部。3. AQS的子类实现Java并发包中ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基于AQS实现的子类只需重写tryAcquire独占获取锁、tryRelease独占释放锁等方法无需关注队列管理、线程挂起/唤醒等底层逻辑。4. AQS的核心优势将同步器的底层逻辑抽象化统一管理队列和线程状态减少子类的开发成本保证并发工具的一致性。16. CAS和AQS的关系√标准回答CASCompare and Swap比较并替换是AQS实现的核心基础二者是“依赖与被依赖”的关系——CAS为AQS提供原子操作支持确保AQS中同步状态state变量的修改是原子性的避免并发修改导致的错误。具体联系1. AQS内部使用CAS修改state变量执行acquire获取锁方法时AQS会尝试通过CAS将state从0修改为1独占模式若修改成功说明获取锁成功若修改失败证明资源已被占用线程会被放入等待队列。2. 释放锁时的CAS操作执行release释放锁方法时AQS会通过CAS将state的值恢复如ReentrantLock中state减1后若为0则释放锁保证释放锁操作的原子性。扩展知识点1. CAS的作用AQS的核心是state变量的原子性修改而Java中普通变量的修改不是原子操作CAS通过CPU的原子指令实现确保state的修改“要么成功要么失败”无需加锁。2. AQS中CAS的具体应用- 独占模式获取锁tryAcquire方法中通过CAS修改state竞争锁- 共享模式获取锁tryAcquireShared方法中通过CAS修改state如Semaphore中许可数递减