面试题高优版
第一部分并发编程线程池 阻塞队列Q1ArrayBlockingQueue和LinkedBlockingQueue有什么区别各自适用什么场景参考答案维度ArrayBlockingQueueLinkedBlockingQueue底层结构数组连续内存链表节点分散锁机制一把锁ReentrantLock两把锁takeLockputLock存/取能否并行❌ 不能同一把锁✅ 能不同锁容量必须指定有界可指定默认Integer.MAX_VALUE无界性能高并发较低更高适用场景低并发、需要精确控制内存高并发、多生产者多消费者追问为什么LinkedBlockingQueue性能更高因为存和取操作使用不同的锁可以真正并行。同时使用AtomicInteger无锁地维护count避免了两把锁之间需要额外同步。Q2ConcurrentHashMap如何实现线程安全的计数器参考答案使用ConcurrentHashMap做计数器时不能写成map.put(key, map.get(key) 1)因为读-改-写不是原子操作。推荐写法// 方式一使用 compute原子操作counter.compute(key,(k,v)-vnull?1:v1);// 方式二使用 AtomicInteger 作为值性能更高AtomicIntegeraicounter.computeIfAbsent(key,k-newAtomicInteger(0));ai.incrementAndGet();追问为什么compute是线程安全的ConcurrentHashMap.compute()在操作期间会锁住该 key 对应的桶bucket保证读取旧值 → 执行计算 → 写入新值整个过程是原子性的。Q3线程池的队列有哪些如何影响线程池行为参考答案队列特点对线程池的影响LinkedBlockingQueue有界/无界吞吐量高无界时maxPoolSize失效ArrayBlockingQueue有界性能稳定队列满后才创建非核心线程SynchronousQueue容量为0直接交接立即创建新线程直到maxPoolSizePriorityBlockingQueue按优先级出队无界任务按优先级执行DelayQueue延迟出队用于定时/延迟任务追问生产环境为什么推荐使用有界队列无界队列可能导致任务无限堆积最终OOM。有界队列配合合理的拒绝策略可以保护系统稳定性。Q4阻塞队列中notEmpty和notFull是如何工作的参考答案它们是ArrayBlockingQueue内部的两个Condition条件变量配合ReentrantLock使用条件关联角色await()时机signal()时机notEmpty消费者队列为空时消费者等待生产者放入元素后唤醒消费者notFull生产者队列满时生产者等待消费者取出元素后唤醒生产者追问为什么用while而不是if来检查条件防止虚假唤醒spurious wakeup线程从await()返回后必须重新检查条件是否满足。Q5LinkedBlockingQueue中为什么在锁外执行signalNotEmpty()参考答案put()方法在入队后如果发现旧count 0队列之前为空需要唤醒消费者。这个唤醒操作被设计在putLock外部执行if(c0){signalNotEmpty();// 在 putLock 外面}原因缩短临界区减少putLock的持有时间提升吞吐量。避免锁传递开销signalNotEmpty()需要获取takeLock如果在putLock内获取会造成锁的交叉等待和上下文切换。解耦存与取putLock只管入队takeLock只管唤醒职责清晰。第二部分数据库InnoDBQ6Redo Log和Binlog有什么区别维度Redo LogBinlog所属层InnoDB 存储引擎MySQL Server 层日志内容物理日志页级修改逻辑日志SQL 语句/行变更写入方式循环写入覆盖追加写入保留历史用途崩溃恢复保证持久性主从复制、数据恢复Q7为什么需要Redo Log它解决了什么问题参考答案Redo Log解决的核心矛盾是Buffer Pool 的高性能 vs 数据的持久性。如果每次修改都直接写数据页16KB 数据页是随机 I/O极慢每次需要寻道。如果只写 Buffer Pool内存易失宕机会丢失数据。解决方案事务提交时将修改操作以顺序追加的方式写入Redo Log顺序 I/O极快数据页异步刷盘。宕机后通过重放Redo Log恢复数据。这就是WALWrite-Ahead Logging的核心思想先写日志再写数据。Q8Buffer Pool中的脏页什么时候刷到磁盘参考答案脏页刷盘是异步的由后台线程触发不随事务提交而刷盘。触发方式说明定时触发page_cleaner线程默认每秒执行一次定量触发脏页比例超过innodb_max_dirty_pages_pct默认 90%Redo Log 写满需要推进 Checkpoint强制刷盘会阻塞所有用户线程Buffer Pool 空间不足LRU 淘汰时脏页需先刷盘再淘汰MySQL 正常关闭所有脏页刷盘后才能退出手动触发FLUSH TABLES等命令关键结论事务提交只保证 Redo Log 落盘不保证脏页落盘。Q9innodb_flush_log_at_trx_commit三个值的区别值行为安全性性能1默认每次提交write()fsync()✅ 最高不丢数据❌ 最差2每次提交仅write()到 OS Cache每秒fsync()⚠️ 宕机丢 1 秒数据✅ 较好0提交时不写后台线程每秒write()fsync()⚠️ 宕机丢 1 秒数据✅ 最好核心write()只是将数据从程序内存拷贝到 OS Cachefsync()才是真正落盘。Q10有了Redo LogMySQL 宕机还会丢数据吗参考答案innodb_flush_log_at_trx_commit 1默认不会丢任何已提交事务的数据。 2或 0可能丢失最近 1 秒内已提交事务的数据。所以答案是默认情况下不会丢但如果为了性能调整了参数就可能丢。Q11Buffer Pool、Redo Log Buffer、OS Cache有什么区别参考答案维度Buffer PoolRedo Log BufferOS Cache位置InnoDB 进程内存InnoDB 进程内存操作系统内核内存存储内容完整数据页16KBRedo Log 记录512B 日志块文件页缓存作用读写缓存避免磁盘 IO事务持久化顺序写入优化所有文件 IO 的中转站刷盘时机异步后台线程事务提交时触发由fsync()或 OS 决定数据安全性易失宕机丢失易失需fsync()落盘易失断电丢失第三部分架构与设计Q12状态机StateMachine中“有状态”和“无状态”如何理解有状态服务会记住之前的信息。例如 Spring StateMachine 的一个实例保存了当前状态下次事件触发时不需要传入当前状态。无状态服务记不住每次请求必须带齐所有信息。例如 COLA 每次调用都需要传入currentState状态机本身只是一个查表的工具。类比有状态 老主顾面馆老板记住你的口味无状态 标准化面馆每次来都要重新点单Q13规则引擎 Easy Rules 和表达式引擎 Aviator 的区别维度Easy RulesAviator定位轻量级规则引擎高性能表达式引擎解决问题替代if...else管理独立规则动态计算公式、表达式求值动态性弱修改需重启强表达式可存储外部热更新性能毫秒级极高编译为字节码适用场景促销规则、数据校验动态公式、风控评分好的已按要求删除Q7 及之后的所有题目。更新后的面试题合集如下第三部分数据库InnoDB 进阶Q1Undo Log是什么什么时候生成参考答案Undo Log是 InnoDB 存储引擎为实现事务的原子性Atomicity和MVCC多版本并发控制而维护的一份日志。核心内容记录的是逻辑日志。INSERT对应一条DELETEUPDATE对应一条反向UPDATE用于回滚。存储位置共享表空间ibdata1或独立的 Undo 表空间持久化在磁盘上。生成时机INSERT操作生成TRX_UNDO_INSERT_REC记录主键信息用于回滚时删除。UPDATE操作生成TRX_UNDO_UPD_EXIST_REC记录修改前的旧版本数据。DELETE操作生成类似UPDATE的 Undo Log标记删除并记录旧版本。关键时机点在数据页被修改之前生成先写日志后写数据。事务执行期间每次 DML 操作都会立即生成对应的 Undo Log。事务提交时不会立即删除需要保留到没有更早的事务需要看到这些旧版本时由后台Purge 线程清理。Q2Undo Log和MVCC的关系是什么参考答案Undo Log是 MVCC 的数据基石。版本链每一行数据被修改时旧版本写入Undo Log行头隐藏字段DB_ROLL_PTR指向 Undo Log形成版本链。Read View事务查询时生成 Read View记录活跃事务 ID 列表。可见性判断InnoDB 从当前版本开始通过DB_ROLL_PTR沿版本链回溯找到第一个符合可见性规则的版本。DB_ROLL_PTRDB_ROLL_PTR当前数据行DB_TRX_ID101Undo Log版本1DB_TRX_ID100Undo Log版本0DB_TRX_ID99一句话Undo Log存数据Read View定规则两者结合实现 MVCC。Q3什么是“快照读”和“当前读”举例说明。参考答案维度快照读Snapshot Read当前读Current Read读取版本历史版本事务开始时或 Read View 生成时最新已提交版本是否加锁不加锁加锁共享锁或排他锁典型操作普通SELECTSELECT ... FOR UPDATE、UPDATE、DELETE、INSERT举例-- 事务 ASTARTTRANSACTION;SELECTbalanceFROMaccountWHEREid1;-- ① 快照读: 1000-- 事务 B 修改并提交UPDATEaccountSETbalance800WHEREid1;COMMIT;-- 事务 A 再次查询SELECTbalanceFROMaccountWHEREid1;-- ② 快照读: 1000可重复读SELECTbalanceFROMaccountWHEREid1FORUPDATE;-- ③ 当前读: 800Q4快照读和当前读分别适用于什么场景参考答案场景推荐读取方式理由报表统计、数据分析快照读数据量大避免加锁阻塞商品/文章详情页快照读读多写少高并发下性能优先库存扣减、余额扣减当前读FOR UPDATE必须读取最新数据防止超卖执行UPDATE/DELETE当前读自动必须基于最新数据版本操作防重复提交、唯一性校验当前读LOCK IN SHARE MODE避免并发场景下的重复写入Q5Binlog是什么有几种格式参考答案BinlogBinary Log是 MySQL Server 层的二进制日志记录所有对数据库产生变更的操作。核心用途主从复制主库写入 Binlog从库读取并重放。数据恢复配合全量备份实现时间点恢复Point-in-Time Recovery。数据审计分析变更操作排查安全问题。三种格式格式记录方式优点缺点适用场景STATEMENT记录 SQL 语句日志量小节省 I/O非确定性函数可能导致主从不一致数据一致性要求不高的场景ROW记录每行数据的变更数据一致性高MySQL 8.0 默认日志量可能较大金融、交易等强一致性场景MIXEDMySQL 自动切换兼顾性能与一致性行为有一定复杂性通用业务场景稳妥选择Q6什么是 MySQL 的二阶段提交2PC解决了什么问题参考答案MySQL 的二阶段提交是 InnoDB 为了协调Redo Log和Binlog两份日志之间的一致性而引入的内部机制。解决的问题Redo Log 写了但 Binlog 没写或反过来导致主从不一致。流程阶段一Prepare ① 执行 SQL在 Buffer Pool 中修改数据页 ② 写入 Redo Log状态标记为 prepare ③ 写入 Binlog 阶段二Commit ④ Binlog 写入成功后将 Redo Log 状态更新为 commit ⑤ 根据参数刷盘返回“提交成功”崩溃恢复判断逻辑Redo Log 状态Binlog 状态恢复决策prepare不存在回滚prepare存在且完整提交commit存在且完整提交