面试知识

Java(编程语言)并发与算法失败追问

91-高频追问题库 面试知识整理。

Java(编程语言)并发与算法失败追问

本册只负责编排失败追问、证据路径和恢复表达。集合、算法与并发机制分别回链 091014 唯一正文;项目事实等级沿用 模块 15 唯一事实账本,不在此建立第二套迁移账本。所有量级均标明 E0(待核对)、E1(直接证据)、E2(既有材料映射)或 E3(演练设计)。

Java(编程语言)并发与算法失败追问证据路线

正式图解读: 六个并行分支表示集合/算法、可见性、锁与 CAS(比较并交换)/AQS(抽象队列同步器)、线程池、死锁、线程上下文与并发容器六类假设;汇合箭头要求它们都回到同一证据合同。正常路径在证据足够后执行可回滚止血、根因与历史状态修复、故障注入验证和复盘;失败路径不猜根因,而是降为 E0(待核对)并补采证据。图的前提是业务键、版本和时间窗已固定,结论是技术信号必须与库存、任务或通知账本共同裁决。

1. 集合与算法边界失败

1.1 可变键、复合操作与输入边界

本节按“现象—假设—证据—止血—修复—验证—复盘”处理集合错乱、算法越界和复杂度退化,最终业务事实仍由库存流水、任务账本或持久化状态裁决。

flowchart LR
    A[固定失败输入与业务键] --> B[复算去重和排序结果]
    B --> C{结果是否可重复}
    C -->|否| D[检查可变键和并发复合操作]
    C -->|是| E[检查首尾下标和复杂度]
    D --> F[保存线程时序与桶位证据]
    E --> G[保存输入规模与分支轨迹]
    F --> H[隔离问题批次]
    G --> H
    H --> I[修复键模型或算法边界]
    I --> J[以流水和守恒式验收]

图解读: 节点从固定输入开始,箭头表示逐步排除随机噪声;正常路径经过复算后进入边界检查,失败路径则暴露同一输入不可重复,优先怀疑共享可变状态。前提是保留原始订单行、排序条件和业务键。最终结论不能停在“集合修好了”,还要用库存流水或任务账本验证业务守恒。

阶段集合或算法现象可证伪假设关键证据动作与退出条件
现象聚合后少一行、重复行或顺序漂移输入重复或边界漏算原始输入摘要、排序前后数量固定同一失败样本
假设查不到已写入键参与哈希的字段被修改写入与查询时的键字段、哈希值复制不可变键后可稳定复现
证据单线程正确、并发少计读改写被拆开操作时序、线程数、最终计数原子复合操作后差值归零
止血批次结果不可相信继续执行会扩大库存差异问题批次、仓库与 SKU(库存单位)范围暂停批次并保留重放输入
修复首尾元素漏处理循环上下界错误分支覆盖和最小反例空、单元素、重复值均通过
验证与复盘代码结果恢复历史库存仍可能不一致流水数量、条件更新结果、对账差异差异清零且故障注入稳定通过

数据演绎 1:批量聚合和算法边界。 E3(演练设计)输入为 10,000 条订单行,其中 400 条是同一请求号的重复投递,另有 20 条数量为 0 的非法行。正确有效输入是 10,000 - 400 - 20 = 9,580 条。若错误循环使用 i < size - 1,还会漏掉最后 1 条,输出 9,579 条;差值率为 (9,580 - 9,579) / 9,580 ≈ 0.0104%,比例很小但可能对应一笔真实库存。状态变化是“原始—去重—校验—聚合—条件扣减”;观测信号包括各阶段行数、数量和摘要。结论是数量大致相等不能替代逐阶段守恒,最小边界用例必须覆盖空集合、单元素、末元素、重复键和极端数值。

失败注入、证据链与项目落地: 注入三类故障:聚合后修改仓库字段、让两个线程执行分离的读取与写回、把分页末页设置为恰好一条。现象分别是键失联、计数少扣和末行遗漏;证据是固定输入的桶位变化、线程交错轨迹和分页游标。止血为暂停问题批次并冻结相关仓库与 SKU(库存单位)的自动重放;修复后用相同输入重复 100 轮,再以数据库条件更新、唯一请求流水和库存守恒复核。集合机制和修复方案来自 Java(编程语言)集合项目唯一正文,属于 E2(既有材料映射);上述数量与轮次均为 E3(演练设计),生产影响为 E0(待核对)。

热门面试题

  1. 问题(基础题):为什么可变对象不适合作为哈希键?

    • 考点:哈希定位、对象相等、生命周期。
    • 回答思路:从写入桶位与查询桶位可能不一致展开。
    • 详细答案:对象写入哈希容器后若参与哈希计算的字段变化,节点仍留在旧桶,查询却按新哈希值寻找,可能出现“对象仍占内存但查不到、删不掉”的现象。应使用不可变业务键,并让可变状态留在值对象中。
    • 进阶追问:只保证相等判断不变是否足够?
    • 进阶回答:不够;相等对象还必须产生一致哈希值,且写入后的键字段不能再变化。
  2. 问题(原理题):并发容器为何不能自动保证读改写安全?

    • 考点:单操作安全、复合操作、原子边界。
    • 回答思路:区分容器方法安全与业务步骤原子性。
    • 详细答案:线程安全容器通常只保证单次方法调用的并发安全,把读取、计算、写回拆成三步后仍可能丢失更新。应使用容器提供的原子复合方法,或把最终裁决收敛到数据库条件更新和唯一流水。
    • 进阶追问:加一把全局锁是否最稳妥?
    • 进阶回答:它可能保证单进程正确,却扩大无关键竞争,也无法覆盖多实例;应按业务键缩小协调范围。
  3. 问题(项目题):WMS(仓储管理系统)批量预占出现少扣或重复扣怎样排查?

    • 考点:集合聚合、算法边界、库存权威事实。
    • 回答思路:先复算输入输出,再沿聚合键、并发写入和库存流水取证。
    • 详细答案:先固定订单行、仓库、SKU(库存单位)和请求号,复算去重前后数量;再检查组合键是否可变、读改写是否拆分、分页或二分边界是否漏首尾。止血时暂停问题批次并隔离热点键,修复后用数据库条件更新、唯一流水与对账证明没有超卖和漏扣。
    • 进阶追问:本地复算一致能否证明线上正确?
    • 进阶回答:不能;还要核对并发时序、跨实例写入、事务结果和历史流水。

2. 内存可见性与原子性失败

2.1 JMM(Java 内存模型)、发布与丢失更新

本节聚焦“日志显示已写入但其他线程仍看不到”、停止标志失效、计数丢失和对象未安全发布等问题,证据以线程状态、读写路径和业务结果交叉裁决。

sequenceDiagram
    participant C as 控制线程
    participant M as 共享内存状态
    participant W as 工作线程
    participant L as 任务账本
    C->>M: 写入停止状态
    alt 建立可见性关系
        M-->>W: 后续读取观察到新值
        W->>L: 保存检查点并退出
    else 普通字段或阻塞未返回
        W->>W: 继续旧循环或等待
        C->>L: 记录停止超时
        C->>W: 发出中断并隔离副作用
    end

图解读: 四个参与者分别表示控制方、共享状态、工作线程和权威任务账本;箭头表示状态发布、读取和退出确认。正常路径依靠可见性关系观察停止状态并保存检查点,失败路径可能是普通字段未刷新,也可能是线程卡在阻塞调用。前提是停止只表示协作请求。结论是“指令已发送”不等于“任务已停止”,必须以账本状态和外部副作用停止共同验收。

现象首要假设证据止血修复与验证
停止标志偶发无效普通字段缺少可见性连续线程栈仍在旧循环禁止新副作用并隔离任务可见状态加安全检查点
计数少于理论值自增发生丢失更新期望值、实际值、线程交错暂停依赖该计数的结算原子更新或分片归并
对象字段偶发默认值引用未安全发布构造、赋值和读取路径回退相关版本建立可靠发布边界
中断后仍运行中断被吞或阻塞不响应中断位、异常日志、栈顶方法摘除执行者恢复中断并协作退出
日志新、行为旧日志与业务状态不在同一顺序边界时间、线程、账本版本以账本冻结推进统一版本和提交点

数据演绎 2:可见不等于原子。 E3(演练设计)设 8 个线程各执行 100,000 次自增,理论结果为 8 × 100,000 = 800,000。若普通或仅可见计数器最终为 632,000,丢失量为 800,000 - 632,000 = 168,000,丢失率为 168,000 / 800,000 = 21%。具体实际值受调度影响,不能把 21% 当固定性能结论;可复算的是理论值、实际值与差值公式。状态变化是每次“读取旧值—加一—写回”,两个线程读取同一旧值时只保留一次结果。观测信号为每轮结果分布、失败重试和处理器占用。修复后要求多轮得到精确 800,000,或使用分片计数时归并结果精确一致。

失败注入、证据链与项目落地: 在 Runner(执行器)演练中把停止字段替换为普通布尔值,同时让任务阻塞于慢外部调用;另在 IoT(物联网)报警统计中把复合自增改为非原子操作。先记录控制指令、线程栈、中断位、计数输入与最终账本,再分别验证“未见状态”和“阻塞未返回”两种假设。止血以停止新领取、隔离外部副作用和保留原任务世代为主。修复使用可见状态、限时调用、中断传播和原子计数,但最终仍由任务状态、报警原始事件数与通知账本裁决。机制依据 JMM(Java 内存模型)唯一正文volatile(可见性关键字)唯一正文,均为 E2(既有材料映射);是否已在生产采用为 E0(待核对)。

热门面试题

  1. 问题(基础题):volatile(可见性关键字)能解决哪些问题?

    • 考点:可见性、有序性、原子性边界。
    • 回答思路:先说明读写可见和顺序约束,再排除复合更新。
    • 详细答案:volatile(可见性关键字)适合发布状态和停止标志,使写入对后续读取可见并建立顺序关系;但自增包含读取、计算、写回,多个线程仍可能覆盖彼此结果。
    • 进阶追问:计数器改为 volatile(可见性关键字)为何仍少计?
    • 进阶回答:因为可见不等于复合操作不可分割,应改用原子类、锁或分片累加后归并。
  2. 问题(原理题):什么是安全发布失败?

    • 考点:对象初始化、引用发布、先行发生关系。
    • 回答思路:说明引用可见不代表构造结果必然完整可见。
    • 详细答案:对象若通过无同步共享字段提前暴露,其他线程可能观察到引用却看见默认字段或旧状态。应通过静态初始化、锁、volatile(可见性关键字)引用、并发容器或任务提交边界建立可靠发布关系。
    • 进阶追问:构造函数执行完就一定安全吗?
    • 进阶回答:只有引用通过安全边界发布才成立;构造完成本身不能替代线程间同步关系。
  3. 问题(故障题):Runner(执行器)停止指令偶发不生效如何取证?

    • 考点:可见性、阻塞中断、任务状态机。
    • 回答思路:区分未看到标志、阻塞未返回和外部副作用不可撤销。
    • 详细答案:连续采集线程栈,确认工作线程是在循环读取普通字段、阻塞于输入输出,还是吞掉中断后继续运行;再核对停止指令时间、任务世代和提交记录。止血是隔离任务并阻止新副作用,修复是用可见状态、中断协议和检查点协作退出,不能用强杀线程伪装完成。
    • 进阶追问:收到中断是否必须立刻退出?
    • 进阶回答:不一定;应在安全边界释放资源、记录检查点,并让上层状态机决定取消或重试。

3. 锁、CAS(比较并交换)与 AQS(抽象队列同步器)失败

3.1 竞争、自旋、排队与取消传播

本节覆盖热点锁、CAS(比较并交换)重试放大、AQS(抽象队列同步器)排队和超时取消,强调本地同步只保护进程内状态。

stateDiagram-v2
    [*] --> 尝试获取
    尝试获取 --> 执行临界区: 获取成功
    尝试获取 --> 进入等待队列: 获取失败
    进入等待队列 --> 阻塞等待: 前驱尚未释放
    阻塞等待 --> 尝试获取: 被唤醒
    阻塞等待 --> 取消节点: 超时或中断
    取消节点 --> [*]
    执行临界区 --> 释放并唤醒
    释放并唤醒 --> [*]

图解读: 状态节点描述同步器中一次获取的生命周期,箭头条件区分成功、排队、唤醒和取消。正常路径是获取、执行、释放并唤醒后继;失败路径是热点竞争导致反复唤醒,或业务吞掉中断让已取消任务仍继续。前提是临界区有限且释放动作位于可靠清理路径。结论是排队机制只能管理线程获取,不能替代库存条件、任务世代或跨实例幂等。

机制适用前提典型失败证据退出或修复条件
synchronized(同步锁)临界区短、单进程互斥锁内慢调用扩大阻塞持锁栈、等待锁地址慢调用移出锁且阻塞下降
CAS(比较并交换)冲突低、状态简单热点自旋和 ABA(值往返变化)风险失败次数、重试分布分片、版本或改用排队
AQS(抽象队列同步器)需要阻塞队列与取消中断吞掉、取消节点堆积队列长度、等待年龄正确传播取消并缩短持有
公平锁顺序比吞吐重要唤醒切换成本高等待分位、切换次数证明公平收益覆盖成本
本地分片锁同实例热点整形跨实例同时进入实例与业务键维度日志共享存储承担最终裁决

数据演绎 3:热点 CAS(比较并交换)与排队成本。 E3(演练设计)设 32 个线程同时更新一个热点计数,每次成功更新平均伴随 7 次失败重试,完成 100,000 次有效更新需要约 100,000 × (1 + 7) = 800,000 次比较,失败比较为 700,000 次,失败占比 700,000 / 800,000 = 87.5%。若分成 16 个稳定业务分片,演练观察平均失败重试降至 0.5 次,则总比较约 100,000 × 1.5 = 150,000 次,减少 650,000 次。该结果只说明冲突模型,不能宣称生产提升;状态从读取版本、比较、成功或失败重试演进,观测信号是重试次数、处理器占用、等待分位和业务完成率。

失败注入、证据链与项目落地: 对 WMS(仓储管理系统)热点 SKU(库存单位)同时注入锁内下游延迟、CAS(比较并交换)高冲突和等待超时。按仓库与 SKU(库存单位)聚合线程等待、重试数、数据库条件更新失败和请求年龄,先限流热点键并把通知移出临界区;不能用扩大线程池掩盖串行瓶颈。修复可采用稳定分片、短临界区、可取消等待和数据库条件更新,验证包括技术侧等待分位下降与业务侧库存不为负、重复流水为零。机制回链 CAS(比较并交换)与 AQS(抽象队列同步器)唯一正文;项目方案属于 E2(既有材料映射),所有演练冲突数据属于 E3(演练设计),真实峰值为 E0(待核对)。

热门面试题

  1. 问题(基础题):CAS(比较并交换)失败为什么需要重试上限?

    • 考点:乐观并发、热点竞争、活锁风险。
    • 回答思路:从冲突概率与无效计算成本回答。
    • 详细答案:低冲突时重试成本小,热点键下大量线程会反复读取和失败,消耗处理器且没有业务进展。应采用退避、分片、排队或锁,并让业务超时限制总预算。
    • 进阶追问:CAS(比较并交换)一定比锁快吗?
    • 进阶回答:不一定;高冲突、长临界区或需要公平时,阻塞排队可能更稳定。
  2. 问题(原理题):AQS(抽象队列同步器)如何支持可取消等待?

    • 考点:同步状态、等待队列、唤醒与取消。
    • 回答思路:按获取失败、入队、阻塞、唤醒和重新竞争解释。
    • 详细答案:线程获取同步状态失败后进入等待队列并阻塞,前驱释放时唤醒后继重新竞争;超时或中断会把节点标为取消并在后续遍历中跳过。业务代码仍须正确传播中断和释放资源。
    • 进阶追问:公平锁为何吞吐可能较低?
    • 进阶回答:严格按队列顺序减少插队机会,会增加挂起、唤醒和上下文切换成本。
  3. 问题(项目题):热点库存锁竞争时为何不能直接扩大线程池?

    • 考点:串行瓶颈、竞争放大、最终裁决。
    • 回答思路:说明更多线程只会增加同一资源的等待者。
    • 详细答案:同一仓库与 SKU(库存单位)的更新受同一业务不变量约束,扩大线程数会增加锁等待、CAS(比较并交换)失败和数据库冲突。应先按热点键限流或排队,再用条件更新和幂等流水裁决结果。
    • 进阶追问:分片锁越多越好吗?
    • 进阶回答:不是;分片必须与业务键和容量匹配,过多会增加路由、内存和跨分片协调成本。

4. 线程池、异步任务与背压失败

4.1 耗尽、无界排队、拒绝与恢复

本节覆盖线程池耗尽、队列积压、拒绝策略副作用、上下游重试风暴和异步任务恢复,强调队列长度是延迟与内存承诺。

flowchart TD
    A[下游耗时升高] --> B[工作线程全部占用]
    B --> C[有界队列接近上限]
    C --> D{任务是否可延后}
    D -->|可延后| E[写入任务账本并背压入口]
    D -->|不可延后| F[返回明确繁忙并退避]
    C --> G[错误策略继续同步执行]
    G --> H[入口线程或消息拉取线程阻塞]
    H --> I[超时与重复投递]
    I --> B
    E --> J[按完成率分批恢复]
    F --> J

图解读: 上半部分是有界容量下的正常保护路径,箭头从慢依赖传到线程、队列和业务分类;下半部分是错误拒绝策略形成的反馈环。前提是任务已区分可延后、可丢弃和必须同步完成。结论是拒绝不是异常处理细节,而是业务过载协议;恢复必须让完成率持续高于到达率,不能一次性释放全部积压。

信号组合可证伪假设证据止血恢复验收
活跃线程满、队列上升下游变慢占满工作线程线程栈、下游耗时、连接池等待隔离下游并限制入口完成率高于到达率
队列上升、堆同步上升任务对象携带大上下文对象直方图、单任务大小暂停大任务工作集形成稳定平台
拒绝后入口延迟升高调用方运行阻塞入口拒绝计数、调用线程栈改为明确背压入口延迟和重试回落
消费正常、业务积压不降拉取与业务完成口径混淆提交位点、业务状态、最老年龄暂停无效拉取业务净积压斜率为负
恢复后再次打满放量超过稳定服务率到达率、完成率、队列斜率回退上一档配额完整窗口内无反弹

数据演绎 4:线程池净积压与等待。 E3(演练设计)设 4 个工作线程,单任务平均执行 50ms(毫秒),理论服务率为 4 / 0.05 = 80 个/秒。故障时到达率升到 100 个/秒,净积压为 100 - 80 = 20 个/秒;持续 20 秒后新增 20 × 20 = 400 个任务。若队列容量也是 400,第 20 秒到达的任务仅等待现有队列就约为 400 / 80 = 5 秒,尚未计算执行时间;若每个排队任务保留 128KiB(千字节)上下文,队列占用约 400 × 128KiB = 50MiB(兆字节)。状态变化是接收、排队、执行、完成或拒绝,观测信号为到达率、完成率、最老年龄、拒绝数和堆水位。结论是容量必须同时受时间和内存约束。

失败注入、证据链与项目落地: 在异步导出演练中将对象存储延迟提高到平时的十倍,连续提交大查询并使 5% 的调用方立即重试。证据包保存线程池配置、每秒到达与完成、最老任务年龄、线程栈、任务对象大小和任务表状态。止血为暂停新建大导出、按租户限额、保护在线交易池并保留运行任务检查点;修复为独立有界池、最小任务载荷、流式分片和带预算的重试。恢复按 10%、30%、60% 放量,每档要求净积压下降、堆稳定、文件行数与摘要一致。依据 线程池与异步编排唯一正文异步导出项目串讲;方案为 E2(既有材料映射),数字为 E3(演练设计)。

热门面试题

  1. 问题(基础题):线程池为什么优先使用有界队列?

    • 考点:背压、内存预算、过载显性化。
    • 回答思路:解释无界队列如何把拒绝推迟成进程故障。
    • 详细答案:下游变慢时,无界队列会持续保留任务及其上下文,排队时间超过调用方超时后又触发重试,最终同时造成内存增长和流量放大。有界队列能在预算耗尽时明确拒绝、降级或持久化排队。
    • 进阶追问:队列容量应如何计算?
    • 进阶回答:由可接受等待时间、稳定完成速率、单任务内存和堆预算共同约束,并通过压测校准。
  2. 问题(原理题):调用方运行拒绝策略何时会扩大故障?

    • 考点:背压传播、调用线程职责、事件循环。
    • 回答思路:判断调用线程是否允许执行慢任务。
    • 详细答案:它能让普通提交方自然降速,但若调用线程是请求入口、消息拉取或事件循环,执行慢任务会阻塞接入与确认,诱发超时、重复投递和更大积压。拒绝必须匹配任务可丢、可延后或必须持久化的业务语义。
    • 进阶追问:拒绝后统一重试可以吗?
    • 进阶回答:不可以;应分类处理并使用退避、随机抖动、总次数和最大年龄预算。
  3. 问题(项目题):异步导出任务堆积时如何止血和恢复?

    • 考点:线程池隔离、任务账本、文件交付。
    • 回答思路:保护在线交易,暂停接收大任务,再按任务状态恢复。
    • 详细答案:先隔离导出入口、限制新任务和单租户并发,保留任务表、队列年龄、线程栈和堆水位;运行任务在批次边界记录检查点。恢复时按小批次与稳定完成率放量,使用任务和分片唯一键去重,最终以文件行数、摘要、权限和任务状态共同验收。
    • 进阶追问:只增加工作线程为何危险?
    • 进阶回答:数据库、对象存储和堆仍是共享瓶颈,更多并发可能把压力从队列转移到依赖与内存。

5. 死锁、长临界区与活性失败

5.1 等待环、锁内慢调用与可推进性

本节区分死锁、普通锁竞争、长临界区和活锁,要求用等待关系与多次采样证明“不可推进”,而不是看到 BLOCKED(阻塞)就下结论。

graph LR
    T1[预占线程] -->|持有| L1[库存锁]
    T1 -->|等待| L2[订单锁]
    T2[取消线程] -->|持有| L2
    T2 -->|等待| L1
    L1 --> R[等待环证据]
    L2 --> R
    R --> S[保存线程栈和流水]
    S --> X[隔离问题实例]
    X --> F[统一锁顺序或状态机]

图解读: 两个线程和两把锁构成持有与等待的闭环,箭头方向就是死锁裁决证据。正常路径不应出现回边,所有线程按同一顺序获取资源;失败路径在预占与取消采用相反顺序时闭合。前提是多份线程栈中的锁地址和调用路径一致。结论是先保存等待环和业务流水,再处理可用性;重启只能释放当前锁,不能删除根因。

活性问题是否有等待环多次采样特征常见误判根治方向
普通竞争持有者会变化BLOCKED(阻塞)多就叫死锁缩短临界区、热点分片
长临界区持有者长期停在慢调用盲目扩大线程池将输入输出移出锁并限时
死锁同一等待环稳定存在重启后无现象即完成统一顺序或消除多锁原子区
活锁通常否线程运行但反复回退处理器高就叫死循环随机退避、所有权或排队
饥饿不一定某线程长期没有获取机会平均等待正常即健康公平性、配额和分区隔离

数据演绎 5:锁顺序与等待传播。 E3(演练设计)设预占路径每秒 40 次、取消路径每秒 10 次,两者各需库存锁和订单锁。反向顺序下只要一次交错为“预占持库存等订单、取消持订单等库存”,两个请求就不再完成;若入口仍以每秒 50 次接收,30 秒可新增 50 × 30 = 1,500 个等待或排队请求。若每个请求超时后最多重试 2 次,理论附加尝试上限为 1,500 × 2 = 3,000 次。统一按“订单标识—库存标识”升序后,等待边只能沿同一方向,不再形成该二节点环;仍需压测长临界区。观测信号是等待环、最老请求年龄、入口与完成率差值、重试次数和库存流水未知态。

失败注入、证据链与项目落地: 使用两个受控线程和屏障,让预占先拿库存锁、取消先拿订单锁,再同时申请对方资源。连续采集三份线程栈,按监视器地址绘制等待图,并保存订单、预占与释放流水。止血先关闭问题入口、禁止自动重试和摘除实例,必要重启前记录在途请求;修复统一锁顺序,或将跨对象更新收敛到带版本的状态机与数据库事务。验证不仅要求工具不再报告死锁,还要让 100 轮注入都能完成、库存守恒、取消不倒退、未知态清零。依据 并发线上排障唯一正文;故障为 E3(演练设计),真实事故及恢复时间为 E0(待核对)。

热门面试题

  1. 问题(基础题):如何区分锁竞争和死锁?

    • 考点:等待环、持有关系、可推进性。
    • 回答思路:从线程持有什么、等待什么构造有向图。
    • 详细答案:普通竞争中持锁线程仍能推进并最终释放;死锁要求等待关系形成闭环,环中线程都无法前进。应连续采集线程栈并按锁地址关联持有者和等待者。
    • 进阶追问:工具报告死锁后还要查什么?
    • 进阶回答:还要关联请求、事务和外部副作用,评估重启前后的数据恢复与重复执行风险。
  2. 问题(原理题):统一锁顺序为何能破坏死锁条件?

    • 考点:循环等待、全序、动态资源集合。
    • 回答思路:说明所有路径按同一稳定顺序获取资源后无法成环。
    • 详细答案:若每个线程都按资源标识升序获取锁,等待边只能从较小资源指向较大资源,不可能再回到较小资源形成闭环。动态资源必须先去重排序,并设计获取失败后的释放策略。
    • 进阶追问:超时获取锁能否根治死锁?
    • 进阶回答:它能恢复活性但可能造成重试风暴,根因仍需统一顺序或缩小原子边界。
  3. 问题(故障题):WMS(仓储管理系统)预占与取消互相卡住如何处理?

    • 考点:订单锁、库存锁、在途事务和补偿。
    • 回答思路:先保存等待环,再隔离入口并核对权威流水。
    • 详细答案:采集多份线程栈证明预占持有库存等订单、取消持有订单等库存;止血时停止问题路径并摘除实例,不能只强制解锁。修复统一资源顺序或收敛为状态机条件更新,恢复后对预占、释放和订单状态逐笔对账。
    • 进阶追问:重启实例是否算修复完成?
    • 进阶回答:不算;它只释放当前锁,反向加锁路径和历史未知状态仍然存在。

6. ThreadLocal(线程本地变量)、并发容器与上下文泄漏

6.1 线程复用、弱键强值与复合原子边界

本节覆盖租户或追踪上下文串请求、ThreadLocal(线程本地变量)值滞留、并发容器复合操作失效和统计不一致,重点检查上下文的创建、传播、使用与清理责任。

flowchart RL
    A[请求甲设置租户甲] --> B[线程池工作线程]
    B --> C{执行后是否清理}
    C -->|是| D[线程上下文为空]
    D --> E[请求乙显式设置租户乙]
    C -->|否| F[旧值随线程存活]
    F --> G[请求乙缺少覆盖]
    G --> H[日志串号或权限误判]
    H --> I[隔离消费者并审计写入]
    I --> J[统一装饰器设置与清理]

图解读: 箭头从一次请求的上下文进入长期工作线程,再分成清理与未清理两条路径;正常路径在任务结束后回到空状态,下个任务必须显式设置自己的不可变上下文。失败路径保留旧值,并在下个任务漏设时污染日志或权限。前提是线程会被复用。结论是上下文必须按任务生命周期管理,线程本身不是业务身份。

现象假设证据止血修复与边界
追踪标识串到下一任务异常路径未清理线程名、任务顺序、异常栈暂停消费者并隔离日志finally 统一移除
租户字段错误提交时未显式捕获上下文任务载荷、提交日志、写入审计阻止跨租户写入任务携带最小不可变身份
堆中上下文持续增长动态键和值滞留对象直方图、引用链、线程寿命降并发并受控重启固定键、显式清理、限制载荷
映射计数偶发偏差把近似统计当强一致事实容器操作与账本差异停止用统计推进状态权威状态单独持久化
getput 丢更新复合操作未原子化相同键的线程交错隔离热点键原子复合方法或外部裁决

数据演绎 6:上下文滞留上界。 E3(演练设计)设线程池处理 10,000 个任务,每个任务动态创建一个 ThreadLocal(线程本地变量)键并保存 64KiB(千字节)上下文,其中 2% 的异常路径未执行清理,则产生 10,000 × 2% = 200 次滞留机会,值体积上界约 200 × 64KiB = 12,800KiB ≈ 12.5MiB(兆字节)。实际残留受线程映射后续清扫影响,不能把 12.5MiB(兆字节)当必然结果。更严重的是错误身份可能污染下一任务。状态变化是设置、使用、异常、清理或滞留;观测信号为任务与线程关联、异常路径、堆中值数量、跨租户审计差异。修复后注入异常仍应让任务结束时上下文为空。

失败注入、证据链与项目落地: 在异步任务装饰器中注入业务异常,让清理语句缺失;随后把另一个租户任务固定调度到同一工作线程,并注入并发映射的分离读改写。证据包含任务载荷租户、线程名、追踪标识、权限判定、数据库审计和堆引用链。止血按高风险安全事件处理:暂停受影响消费者、阻止跨租户写入、逐笔核对实际数据;日志串号本身只证明上下文污染,不能直接断言数据越权。修复为不可变显式上下文、任务装饰器 try/finally 和原子复合容器操作。依据 ThreadLocal(线程本地变量)与并发容器唯一正文;机制为 E2(既有材料映射),数据为 E3(演练设计),生产影响为 E0(待核对)。

热门面试题

  1. 问题(基础题):ThreadLocal(线程本地变量)为什么会在线程池中泄漏?

    • 考点:线程寿命、弱引用键、强引用值。
    • 回答思路:比较请求生命周期与工作线程生命周期。
    • 详细答案:线程池工作线程长期存活,若任务设置值后未清理,值会随线程继续被引用;键被回收也不代表值立即释放。必须在明确边界设置,并在 finally(最终清理)中调用 remove()
    • 进阶追问:设置空值是否等价?
    • 进阶回答:不等价;明确移除更能结束条目生命周期,也要统一清理第三方库创建的上下文。
  2. 问题(原理题):并发映射的 size() 为什么不适合作为强一致裁决?

    • 考点:并发统计、弱一致观察、业务事实。
    • 回答思路:区分监控近似值和事务判定值。
    • 详细答案:并发更新期间统计可能通过分散计数或重试获得瞬时结果,读到的是变化过程中的观察值。它适合监控趋势或容量近似,不应决定库存、支付或任务是否完成;强一致裁决必须来自状态记录、唯一约束与账本。
    • 进阶追问:加锁读取大小是否值得?
    • 进阶回答:通常不值得;全局协调会破坏吞吐,应改为维护有明确语义的独立计数或查询权威存储。
  3. 问题(项目题):异步任务出现租户串号如何建立证据链?

    • 考点:上下文传播、线程复用、权限边界。
    • 回答思路:按提交任务、工作线程、日志字段和数据写入关联。
    • 详细答案:固定错误任务号、租户、线程名和时间窗,检查提交时是否显式复制不可变上下文、执行前是否覆盖旧值、异常路径是否清理。止血是暂停受影响消费者并按权威任务租户校验写入;修复使用任务装饰器统一设置和移除,并增加跨租户并发故障注入。
    • 进阶追问:日志串号就能证明数据越权吗?
    • 进阶回答:不能;还要核对权限判定和实际读写记录,但应按高风险事件先隔离和审计。

7. 综合口述题

  1. 问题:WMS(仓储管理系统)库存聚合偶发少一条,如何判断是集合键失效、算法边界还是业务重复?

    • 考点:不可变键、最小反例、阶段守恒、库存裁决。
    • 回答思路:固定失败输入,逐阶段复算,再用可证伪证据排除三类假设。
    • 详细答案:集合症状只是线索;必须把原始行、去重、校验、聚合和持久化分别计数,最终以库存流水和条件更新结果验收。
    • 进阶追问:如果重放不再复现怎么办?
    • 进阶回答:保留原线程时序、键字段快照和版本,扩大采样但不把偶发消失当作已修复。
    • 口述答案:我先把“少一条”定义为可复算差异,而不是直接归因于 HashMap。现象层固定租户、仓库、SKU(库存单位)、订单行、请求号、版本和时间窗,保存原始输入摘要以及去重、校验、聚合、条件扣减各阶段的条数与数量。假设一是组合键写入后被修改,证据是写入与查询时参与哈希的字段和哈希值不同;假设二是循环、分页或二分边界漏掉首尾,证据是空集合、单元素和末页一条的最小反例稳定失败;假设三是上游重复或乱序,证据是同一稳定请求号出现多次但唯一流水只应生效一次。止血时暂停问题批次和自动重放,隔离相关仓库与 SKU(库存单位),不能为了补齐数量直接再扣一次。修复分别对应不可变值键、覆盖边界的算法和原子去重;并发复合操作还要改成容器原子方法,跨实例最终交给数据库条件更新与唯一流水。验证时用固定种子并发重放至少多个轮次,检查每阶段守恒、库存不为负、重复流水为零和未知态清零。复盘补充键模型评审、属性测试、阶段摘要与告警。这里的故障数据属于 E3(演练设计);真实影响范围和修复收益若无运行记录就是 E0(待核对),不能把本地集合正确说成全局库存一致。补充验收以同一请求号的原始行摘要、唯一流水和库存变更三者逐项相等为准;任一阶段无法闭合就停止重放并保留批次,而不是用人工补行掩盖差异。 补充停止条件是任一阶段摘要无法与输入守恒,或同一请求号出现多个生效流水;此时只冻结批次和保留重放样本。恢复验收要逐行比对稳定键、数量与库存变更,连续回放通过后才解除隔离。
    • 追问 1:可变键修好后能直接重跑吗? 直接回答:不能,先查已有流水和事务结果,防止把未知成功重复执行。
    • 追问 2:数量对上是否代表内容正确? 直接回答:不代表,还要比较稳定业务键、数量和摘要。
    • 追问 3:本地锁能否替代唯一约束? 直接回答:不能,多实例不共享本地锁。
    • 追问 4:最小边界用例有哪些? 直接回答:空、单元素、末元素、重复键、极值和恰好跨页。
    • 对应唯一正文:集合项目排障
  2. 问题:异步导出文件比查询结果少最后一页,怎样做算法边界与并发快照排查?

    • 考点:游标边界、稳定排序、快照一致性、交付校验。
    • 回答思路:先区分算法漏页与导出期间数据变化,再检查分片合并和文件发布。
    • 详细答案:固定查询条件、排序键和快照版本,逐页记录首尾游标、行数与摘要,最终文件未校验前不得对外成功。
    • 进阶追问:使用偏移分页为什么可能重复或遗漏?
    • 进阶回答:导出期间插入删除会移动偏移位置,应固定快照或使用稳定唯一键游标。
    • 口述答案:我先把现象拆成“源查询当时有多少行、任务实际读取多少行、分片产出多少行、合并文件包含多少行、对外文件是否就是该版本”五个问题。假设一是循环条件使用了错误的末页判断,例如本页小于批次大小时先退出再写入;假设二是排序键不唯一,多个相同时间值在游标切换时重复或遗漏;假设三是偏移分页期间有新增或删除,导致后续页整体移动;假设四是某分片成功写出但合并或上传前状态被误标成功。证据要保存固化后的租户、字段权限、查询条件、快照版本,每页首尾稳定键、行数、摘要、分片状态和最终文件摘要。止血时暂停问题模板的新任务、撤销错误下载地址并保留临时文件,不能只补最后一页后覆盖原文件,因为用户可能已经下载了不完整版本。修复采用固定快照或稳定唯一键游标,先写本页再判断结束,分片与文件都用唯一键和版本;上传、摘要、权限建立完成后才发布。验证以 源快照总行数 = 各分片行数之和 = 合并文件数据行数 复算,并注入恰好整页、末页一条、排序值重复、进程重启和取消竞争。复盘增加文件级校验与发布门禁。规模和故障轮次标 E3(演练设计),真实线上影响保持 E0(待核对)。额外的停止条件是任务状态未进入“可发布”前禁止生成下载地址;恢复验收还要抽取首键、末键和文件摘要,与同一快照的分片清单逐项比对,任何一项不符都只能标记失败并重新生成新版本。 发布停止条件是源快照、分片清单和文件摘要有一项不一致就撤销地址;恢复时必须用同一快照重新核对首尾游标、总行数和权限版本,不能用覆盖旧文件代替验收。
    • 追问 1:只比较文件大小够吗? 直接回答:不够,不同内容可能大小接近,应校验行数、稳定键范围和摘要。
    • 追问 2:重试能换新快照吗? 直接回答:同一任务不能悄悄换,否则用户得到混合口径;应新建有版本的任务。
    • 追问 3:取消后迟到分片怎么办? 直接回答:用任务版本和状态条件拒绝其推进成功。
    • 追问 4:邮件发送成功代表导出成功吗? 直接回答:不代表,邮件只是通知,任务账本和文件校验才是交付事实。
    • 对应项目串讲:异步导出
  3. 问题:Runner(执行器)收到停止命令后仍继续扣费,如何区分可见性、阻塞和状态机缺陷?

    • 考点:可见性、中断协议、外部副作用、任务世代。
    • 回答思路:按控制指令、线程状态、外部请求和账本提交建立时间线。
    • 详细答案:停止命令只是一项协作请求,必须证明工作线程观察到状态、到达安全点并且旧世代不能再提交副作用。
    • 进阶追问:把停止字段改成 volatile(可见性关键字)是否完成修复?
    • 进阶回答:不一定;阻塞调用可能不返回,已发出的外部请求也不能被字段写入撤销。
    • 口述答案:我不会把“命令已写库”当作“执行已经停止”。先固定任务号、执行实例、世代、停止指令时间和扣费请求号,构造从控制面到数据面的时间线。假设一是工作循环读取普通字段,没有可靠可见性关系;证据是线程持续运行旧循环且没有读取新版本。假设二是线程已可见停止状态,但阻塞在无超时的外部调用,证据是连续线程栈停在同一输入输出方法。假设三是中断被捕获后吞掉,日志有异常却继续下一步。假设四是旧执行者已经发出扣费,停止后恢复仍能提交结果,说明缺少世代条件和外部幂等。止血先停止新领取和续租,隔离该任务后续不可逆动作,按稳定请求号查询扣费结果;未知态先查证,不能换请求号重扣。修复包括可见停止状态、有限超时、正确恢复中断、在批次安全点保存检查点,以及提交时携带当前世代;外部扣费仍需幂等键。验证注入线程暂停、网络超时、响应丢失和旧实例恢复,要求旧世代写入为零、同一扣费只生效一次、任务可从可信检查点恢复。复盘把停止延迟、未知态年龄和过期世代写入纳入监控。租约方案属于 E2(既有材料映射)或 E3(演练设计),是否生产已用必须从源码与字段核对。恢复不能只看工作线程退出,还要看到租约不再续期、检查点版本单调前进、扣费请求最终可查证;在这三项未同时满足前,任务保持未知态并禁止新世代接管。 停止条件还包括旧世代仍有续租、扣费响应未查清或检查点未落盘;三者任一存在都不允许新执行者接管。恢复要确认同一请求号只对应一笔成功扣费,并能从最后检查点重跑。
    • 追问 1:能否直接调用线程强制停止? 直接回答:不能,可能在持锁或写一半时破坏状态和资源。
    • 追问 2:中断等于取消吗? 直接回答:不等于,中断是协作信号,任务状态机决定取消结果。
    • 追问 3:外部响应超时如何处理? 直接回答:保持未知态并用原请求号查证。
    • 追问 4:恢复完成看什么? 直接回答:看旧世代写入、重复副作用、积压和任务账本是否共同收敛。
    • 对应唯一正文:线程生命周期与可见性
  4. 问题:配置对象偶发读到默认值,如何证明是未安全发布而不是配置中心延迟?

    • 考点:对象构造、引用发布、版本证据、外部配置链路。
    • 回答思路:同时建立进程内发布与配置版本两条证据链,用对照实验裁决。
    • 详细答案:读取到默认字段可能来自半初始化对象,也可能来自旧配置;必须记录对象身份、字段、发布路径和配置版本。
    • 进阶追问:构造函数没有抛错是否能排除半初始化?
    • 进阶回答:不能;若引用通过缺乏同步的共享字段发布,其他线程仍可能观察不完整状态。
    • 口述答案:我先固定错误请求、实例、线程、配置键、期望版本、实际字段和发生时间,避免把“默认值”直接等同配置中心延迟。外部链路假设是实例尚未收到新版本,证据应表现为版本号旧、监听事件缺失或拉取失败,但同一对象内部字段仍自洽。进程内发布假设是构造后的引用通过普通共享字段提前暴露,证据可能是版本字段已新而其他不可变字段仍为默认值,或者同一版本在不同线程观察不一致。取证时记录对象身份、构造完成日志、引用赋值点、读取点、配置事件版本和线程时序;必要时在隔离环境用屏障放大构造与读取交错。止血可回退到最后完整且校验通过的不可变配置快照,拒绝字段不完整的新对象,不能把空值静默当业务默认。修复采用先完整构造并校验,再通过静态初始化、锁、volatile(可见性关键字)引用或并发容器原子替换快照;配置版本、对象版本和生效时间一并记录。验证注入事件延迟、重复事件和并发读取,要求读者只能看到旧完整快照或新完整快照,不出现混合状态。复盘增加快照校验、版本差异和回退指标。该判断机制为 E2(既有材料映射),生产是否发生过以及影响租户是 E0(待核对)。恢复验收要在所有实例采到同一版本的完整字段摘要后才解除回退;若出现版本前进但摘要不一致,立即停止发布并保留旧快照,不能以单个实例正常读取作为全局通过依据。 停止发布的信号是版本号前进但字段摘要不一致;保留旧快照并阻断读取。恢复要在每个实例观察到同一完整摘要后放行,并验证并发读取没有混合版本。
    • 追问 1:逐字段使用 volatile(可见性关键字)可以吗? 直接回答:不推荐,无法轻易保证多个字段来自同一版本快照。
    • 追问 2:旧完整配置和新半配置选哪个? 直接回答:通常保留旧完整快照并告警,具体由业务安全边界决定。
    • 追问 3:日志顺序能直接证明执行顺序吗? 直接回答:不能,还要结合线程、时间源和同步边界。
    • 追问 4:如何验收回退? 直接回答:确认所有实例版本一致、字段校验通过且业务错误回落。
    • 对应唯一正文:volatile(可见性关键字)与发布
  5. 问题:热点 SKU(库存单位)上 CAS(比较并交换)失败率陡升,怎样止血而不破坏库存正确性?

    • 考点:乐观重试、热点串行化、条件更新、业务不变量。
    • 回答思路:先证明冲突而非计算变慢,再把流量整形与最终裁决分层。
    • 详细答案:CAS(比较并交换)失败率高说明竞争窗口扩大;止血应限制热点键和重试预算,库存底线仍由共享存储条件更新裁决。
    • 进阶追问:改成普通写入能否快速恢复吞吐?
    • 进阶回答:不能,会用错误结果换吞吐,可能造成丢失更新和超卖。
    • 口述答案:我先确认现象是否集中在仓库与 SKU(库存单位)组合,而不是全局处理器或数据库普遍变慢。证据包括每个业务键的到达率、CAS(比较并交换)尝试和失败次数、成功更新数、线程处理器占用、等待分位、数据库条件更新结果以及库存流水差异。假设一是热点竞争让多个线程读取同一旧版本后反复失败;假设二是临界计算或锁内调用变长,扩大冲突窗口;假设三是调用方无上限重试,把一次冲突放大成多次请求。止血先对热点键限流或进入有界排队,限制本地重试次数并使用退避,保护其他仓库和 SKU(库存单位);不能关闭版本条件,也不能把缓存值当最终库存。修复可按稳定业务键分片,将昂贵计算移出更新窗口,低冲突计数继续使用 CAS(比较并交换),极端热点转为单所有者排队或短锁;跨实例最终仍执行 available >= quantity 的数据库条件更新,并写唯一预占流水。验证注入相同热点流量、线程暂停与重复请求,要求失败重试受预算约束、处理器占用回落、完成率稳定,同时库存不为负、重复流水为零、失败请求得到明确结果。复盘记录热点识别、自动降级阈值和恢复放量门禁。所有压测数字是 E3(演练设计),真实收益为 E0(待核对)。热点降级的停止条件是单键重试预算、排队年龄或库存写入未知态任一超限;恢复时先用低并发确认条件更新影响行数与流水一一对应,再逐档放量,不能只凭失败率下降恢复全量。
    • 追问 1:CAS(比较并交换)一定无锁吗? 直接回答:它不使用互斥锁,但可能自旋重试并产生严重协调成本。
    • 追问 2:热点排队是否降低吞吐? 直接回答:可能降低瞬时并发,却能减少无效重试并稳定有效完成率。
    • 追问 3:缓存预扣能做最终事实吗? 直接回答:不能,仍需持久化条件和流水对账。
    • 追问 4:何时从 CAS(比较并交换)切换为锁? 直接回答:当冲突、重试和处理器成本持续高于排队成本并经压测证明时。
    • 对应唯一正文:CAS(比较并交换)与锁体系
  6. 问题:AQS(抽象队列同步器)等待超时后任务仍推进,怎样排查取消传播失败?

    • 考点:等待节点取消、中断传播、业务提交边界。
    • 回答思路:区分同步器已取消、业务吞掉中断和外部请求已发出三种状态。
    • 详细答案:线程离开等待队列不代表业务自动取消;代码必须尊重超时或中断,并在提交前再次校验任务版本。
    • 进阶追问:超时后释放锁就足够吗?
    • 进阶回答:不够;还要阻止后续状态提交,核对已发生的外部副作用并清理资源。
    • 口述答案:我先固定任务号、等待资源、超时时间、线程、任务版本和最终写入,画出“尝试获取、入队、阻塞、超时或中断、业务继续、提交结果”的时间线。假设一是限时获取返回失败后代码忽略返回值,仍执行临界逻辑;证据是没有持有锁却出现业务写入。假设二是等待抛出中断异常后被通用捕获并吞掉,证据是日志记录异常但中断位未恢复、后续步骤继续。假设三是等待确实取消,但此前外部请求已经发出,响应迟到后缺少任务版本检查。取证需要 AQS(抽象队列同步器)队列等待、线程栈、锁获取结果、异常处理路径、外部请求号和任务状态变更。止血时停止该类任务新领取,阻止无当前版本的提交,按原请求号查询外部结果;不能仅清空等待队列。修复要求每个获取结果都有明确分支,中断异常恢复中断位并向上层传播,资源在 finally 中释放,任务提交带状态与世代条件,外部调用使用稳定幂等键。验证注入等待超时、中断、虚假唤醒式重复检查、响应丢失与旧任务恢复,要求取消任务不再推进、资源无泄漏、未知副作用可查证。复盘补充取消率、超时后写入和过期版本提交告警。机制是 E2(既有材料映射),生产缺陷与影响为 E0(待核对)。恢复验收必须同时满足取消任务无新提交、等待节点数量回落、线程中断语义未被吞掉;外部响应若仍未知,就保持任务挂起并由原请求号查证,不能把清空本地队列当作完成。 恢复验收必须同时满足取消任务无新提交、等待节点数量回落、线程中断语义未被吞掉;外部响应若仍未知,就保持任务挂起并由原请求号查证,不能把清空本地队列当作完成。补充停止条件是取消后仍出现业务写入或资源等待不降,恢复以未知副作用全部可查证为准。
    • 追问 1:中断异常可以统一转换成业务失败吗? 直接回答:可以映射,但必须保留中断语义并区分是否已产生副作用。
    • 追问 2:取消节点会永远留在队列吗? 直接回答:通常会在后续队列维护中跳过或清理,但业务不能依赖清理时机。
    • 追问 3:超时后能直接换请求号重试吗? 直接回答:不能,先查原请求结果,避免重复副作用。
    • 追问 4:验证为何要看版本提交? 直接回答:因为线程退出等待后仍可能以旧状态覆盖新执行者结果。
    • 对应唯一正文:AQS(抽象队列同步器)队列
  7. 问题:异步导出线程池队列增长并逼近 OOM(内存溢出),如何形成完整故障回答?

    • 考点:有界容量、对象生命周期、流式处理、恢复验收。
    • 回答思路:用到达率、完成率、单任务内存和队列年龄量化,再保护在线链路。
    • 详细答案:异步只改变等待位置,不自动限制任务数量;队列、批次和并发都必须有上界。
    • 进阶追问:临时增大堆能否作为止血?
    • 进阶回答:可争取取证时间,但若净积压为正只会延后故障,还可能增加垃圾回收停顿。
    • 口述答案:我先确认现象是导出池独立耗尽还是已经传播到在线交易:记录任务到达率、完成率、最老年龄、活跃线程、队列深度、拒绝数、单任务载荷、堆水位、垃圾回收后基线和下游耗时。假设一是对象存储或查询变慢,占满工作线程;假设二是无界队列保留完整查询结果、用户上下文或大闭包;假设三是调用方超时立即重试,使到达率进一步超过完成率。证据用连续线程栈定位阻塞点,用对象直方图比较任务包装对象和字节数组增量,并与任务表逐项关联。止血先暂停新建大导出、按租户限并发、保持支付和库存线程池隔离,对运行任务在批次边界保存检查点;必要时受控重启前保留任务账本和临时文件,不能丢掉未知任务。修复采用有界队列、最小任务标识、主键游标分片、边读边写、有限超时和分类拒绝;任务、分片和文件都有唯一键与版本。恢复按小比例放量,只有完成率持续高于到达率、积压年龄下降、堆形成平台、在线延迟正常、文件行数和摘要一致时继续。复盘加入容量公式和故障注入。规模与阈值均为 E3(演练设计),实际 OOM(内存溢出)事故为 E0(待核对)。 停止扩容的信号是到达率仍高于完成率、最老任务继续增长或堆基线不回落;此时只保留在线交易预算并暂停大导出。恢复验收要按租户小批量放行,核对任务账本、分片行数、文件摘要和内存平台,连续一个完整窗口无反弹后才提升配额;任何失败任务未归档都不能算清空。 只有队列斜率转负且失败任务已分类,才允许继续扩容。
    • 追问 1:队列清空是否代表恢复? 直接回答:不代表,还要确认失败任务、文件正确性和在线服务没有被转移压力。
    • 追问 2:拒绝任务是否会丢数据? 直接回答:入口应先持久化任务或明确未受理,不能返回成功后静默丢弃。
    • 追问 3:为什么按租户限额? 直接回答:防止单个大客户占满共享预算并影响其他租户。
    • 追问 4:何时允许扩容? 直接回答:确认瓶颈可并行且数据库、存储和堆有余量后再灰度扩容。
    • 对应唯一正文:线程池与异步编排
  8. 问题:线程池拒绝触发调用方重试风暴,如何设计止血、修复和验证?

    • 考点:背压反馈、重试预算、任务语义、恢复放量。
    • 回答思路:先画正反馈环,再按可延后、可丢弃和不可丢失分类。
    • 详细答案:拒绝后立即重试会把容量不足变成更高到达率;必须给上游明确反馈并限制重试总预算。
    • 进阶追问:统一采用调用方运行策略是否能自然背压?
    • 进阶回答:只有普通提交线程才可能适用;入口、消息拉取或事件循环线程被阻塞会扩大故障。
    • 口述答案:我会先画出“下游变慢—工作线程占满—队列满—拒绝—立即重试—到达率更高”的反馈环,并用每秒提交、完成、拒绝、重试、最老年龄和调用线程延迟证明它。假设一是重试没有退避和最大次数;假设二是多个层级同时重试,一次失败在网关、服务和任务层被乘法放大;假设三是调用方运行策略让请求入口或消息拉取线程同步执行慢任务,导致确认延迟和重复投递。止血先在最靠近入口的位置限流,关闭非关键自动重试,对可延后任务写入持久任务账本,对明确未受理请求返回可识别繁忙结果;资金、库存等未知结果不能当失败重做,先按原业务键查证。修复建立单一重试责任层,采用指数退避、随机抖动、最大次数和最大年龄,并按错误类别区分可重试、不可重试和未知。线程池使用有界队列,拒绝策略与业务语义绑定。验证注入下游慢和部分失败,要求重试放大倍数受控、到达率不再因拒绝上升、完成率超过新增率、未知态下降;恢复按档放量,任何指标反弹就回退。复盘记录重试预算所有者和容量门禁。数字属于 E3(演练设计),真实风暴结果为 E0(待核对)。 重试停止条件是重试放大倍数继续上升、未知结果积累或入口确认延迟扩大;先切断自动重试并保留原请求号。恢复验收按错误类别和租户分档,只有新增率低于稳定完成率、旧积压下降且未知结果已查证,才恢复下一档流量;同一副作用不能因换请求号再次执行。 同一请求号的成功次数和文件版本也必须保持一一对应。
    • 追问 1:随机抖动解决什么? 直接回答:避免大量客户端在同一退避时刻同步重试。
    • 追问 2:哪些错误不应重试? 直接回答:参数非法、权限拒绝和确定性业务冲突等明确不可重试错误。
    • 追问 3:未知结果为什么先查证? 直接回答:请求可能已成功,只是响应丢失,直接重试会重复副作用。
    • 追问 4:恢复为何分档? 直接回答:验证当前稳定服务率,防止积压与实时流量同时击穿系统。
    • 对应项目排障:异步线程池
  9. 问题:WMS(仓储管理系统)预占与取消形成死锁,如何从线程栈走到业务恢复?

    • 考点:等待环、锁顺序、在途状态、库存对账。
    • 回答思路:先证明环并保留现场,再分可用性恢复、代码修复和数据修复。
    • 详细答案:死锁证据是持有与等待关系闭环;实例恢复后仍要查清每笔预占或释放是否已提交。
    • 进阶追问:工具已打印死锁摘要,还需要多份线程栈吗?
    • 进阶回答:摘要足以确认一次死锁,多份栈有助于圈定稳定路径、影响线程和是否伴随其他长临界区。
    • 口述答案:我先固定实例、版本、告警时间、受影响订单和库存键,连续采集线程栈。若预占线程持有库存锁并等待订单锁,取消线程持有订单锁并等待同一库存锁,锁地址和调用路径闭合,就能确认死锁;大量 BLOCKED(阻塞)但持有者仍变化只能说明竞争。止血前保存完整线程栈、请求日志、事务状态、预占与释放流水,随后关闭问题入口、暂停自动重试并摘除实例;必要重启只是恢复进程活性,不能宣布修复。代码根因通常是两条路径资源顺序相反,修复可将订单与库存标识去重后按稳定全序获取,缩短临界区并把远程调用移出锁;更稳妥时把状态转换收敛到数据库条件更新和版本检查,减少多锁原子区。数据恢复逐笔分类为未执行、已提交、回滚和未知,未知项按业务键与流水查证,不能直接补扣或释放。验证用受控屏障重复构造最坏交错,要求不再形成等待环,且预占、取消、库存可售与冻结数量守恒;同时检查尾延迟和吞吐,防止用粗锁消除死锁却制造严重串行。复盘加入锁顺序规则、静态评审、故障注入与业务对账门禁。演练属于 E3(演练设计),真实事故影响为 E0(待核对)。 等待图出现同一环、线程栈连续不变且业务流水停滞时,立即停止入口和重试;若只有锁等待无环,则保留请求继续取证。恢复必须逐笔确认预占与取消的最终状态,屏障注入连续通过并且可售、冻结和释放数量守恒后,才重新开放相关路径。 未分类的未知流水必须继续隔离,不能随实例重启消失。
    • 追问 1:超时锁能否替代统一顺序? 直接回答:不能,只能打破永久等待,仍可能形成高频回退和重试。
    • 追问 2:重启前为何保存流水? 直接回答:锁会消失,但已提交或未知业务动作不会消失。
    • 追问 3:全局一把锁可行吗? 直接回答:正确性可能简单,但无关库存被串行,吞吐与故障域不可接受。
    • 追问 4:恢复验收最重要的业务量是什么? 直接回答:可售、冻结、已扣和释放流水必须按订单与库存键守恒。
    • 对应唯一正文:并发死锁排障
  10. 问题:大量线程等待同一锁,但没有等待环,如何定位锁内慢调用并避免误报死锁?

  • 考点:长临界区、连续采样、输入输出隔离、超时预算。
  • 回答思路:找同一锁的持有者,观察其是否推进以及栈顶是否为慢依赖。
  • 详细答案:等待者多不等于死锁;持锁线程若停在网络或数据库调用,会形成车队式阻塞但仍可能最终释放。
  • 进阶追问:持有者线程是 RUNNABLE(可运行)就代表处理器计算吗?
  • 进阶回答:不代表,某些网络输入输出也显示该状态,应结合栈顶、处理器采样和下游耗时判断。
  • 口述答案:我先按线程栈中的监视器地址把等待者和持有者关联起来。若几十个线程都等待锁 A,但只有一个持有者,且持有者连续三次采样都停在数据库、网络读取或外部接口调用,同时没有“持有 A 又等待由某个等待者持有的 B”这类回边,就应判断为长临界区或慢依赖放大,而不是死锁。证据还包括锁等待时间、下游尾延迟、连接池等待、持有者请求号和事务时长。止血可隔离慢下游、限制进入该业务键的并发、设置入口背压并暂停非关键动作;不要立即扩大线程池,因为更多线程只会排到同一把锁后面。修复先把参数准备和远程调用移出锁,临界区只保留必须原子检查与状态修改;外部结果回来后用版本或状态条件防止陈旧写入,并设置有限超时与未知态查证。如果业务确实要求同键串行,可采用分片所有者或有界队列,让等待可观测而不是占用大量请求线程。验证注入下游延迟,要求锁持有时间受控、等待分位回落、队列有上界,业务状态不倒退也不重复。复盘区分死锁告警与长持锁告警。机制为 E2(既有材料映射),阈值和结果均是 E3(演练设计)。 长临界区的停止条件是持锁时间或最老等待持续越界,即使没有环也要隔离慢依赖。恢复验收要同时看到持有者栈发生推进、锁等待高分位下降、队列有界,以及移出锁后的版本回写没有覆盖更新;只降低平均延迟不算通过,还要确认业务状态没有倒退。 持锁者完成后还要核对受保护状态的版本连续性。
  • 追问 1:把远程调用移出锁会不会产生竞态? 直接回答:会有陈旧结果风险,因此回写必须带版本或状态条件。
  • 追问 2:怎样证明持有者可推进? 直接回答:多份栈帧变化、请求最终完成和锁所有者更替都能提供证据。
  • 追问 3:锁等待告警看平均值够吗? 直接回答:不够,应看高分位、最大等待和最老请求年龄。
  • 追问 4:何时采用同键队列? 直接回答:业务天然同键串行且热点可隔离、队列容量和失败恢复可明确时。
  • 对应唯一正文:锁与线上证据链
  1. 问题:异步任务日志出现租户串号,如何判断是否存在 ThreadLocal(线程本地变量)上下文泄漏和数据越权?
  • 考点:线程复用、上下文生命周期、安全证据、止血边界。
  • 回答思路:先按高风险事件隔离,再分别证明日志污染、权限误判和实际数据写入。
  • 详细答案:串号能证明上下文有问题,却不能单独证明数据越权;必须关联任务载荷、权限决策和数据库审计。
  • 进阶追问:只在入口清理上下文够吗?
  • 进阶回答:不够;异步工作线程必须在每次任务执行边界显式设置并在 finally 中移除。
  • 口述答案:我会把这类问题按潜在跨租户安全事件处理,先暂停受影响消费者或关闭高风险写入,保留任务、线程和审计现场。现象层固定错误任务号、任务载荷中的真实租户、日志租户、线程名、追踪标识、执行时间和写入对象。假设一是提交方依赖 ThreadLocal(线程本地变量)隐式传播,异步任务没有携带租户;假设二是前一任务异常退出未清理,后一任务又漏设,复用同一工作线程后继承旧值;假设三只是日志上下文未清理,但权限和数据库路由使用任务显式字段,因此没有真实越权。证据链从任务持久化载荷、提交日志、工作线程连续任务顺序、异常栈、权限判定输入、数据库租户条件和审计记录逐层关联。止血不能只改日志,要阻止缺少显式租户的任务执行,对已写数据按权威任务租户逐笔核对。修复使用不可变最小上下文随任务传递,任务装饰器在执行前覆盖全部字段,在 try/finally 中逐项移除,禁止业务权限直接读取遗留线程状态。验证固定少量工作线程,交替执行两个租户并注入异常,要求任务结束后上下文为空、日志与任务一致、跨租户读写为零。复盘增加执行边界测试和审计告警。机制是 E2(既有材料映射),是否造成真实越权为 E0(待核对),未查证前既不能淡化也不能夸大。 只要权限判定租户与任务载荷不一致,就停止该消费者并冻结相关写入;日志恢复不能解除安全处置。恢复验收须完成工作线程上下文清理抽样、权限输入和数据库审计逐笔比对,确认无跨租户读写后再放量,并保留异常任务回放证据。
  • 追问 1:使用可继承线程本地变量能解决吗? 直接回答:不能可靠解决线程池复用,还可能传播过多敏感上下文。
  • 追问 2:调用 set(null) 是否足够? 直接回答:不建议,应明确 remove() 并清理所有相关键。
  • 追问 3:受控重启算修复吗? 直接回答:只清除当前内存状态,未清理路径仍会重现。
  • 追问 4:如何证明没有数据越权? 直接回答:核对权限决策、查询条件、写入审计和业务对象租户,而非只看日志。
  • 对应唯一正文:ThreadLocal(线程本地变量)
  1. 问题:ConcurrentHashMap(并发哈希映射)中的库存计数仍然丢失,为什么容器线程安全不等于业务原子?
  • 考点:单操作安全、复合读改写、热点键、跨实例边界。
  • 回答思路:重放两个线程交错,再把本地计数与权威库存分层。
  • 详细答案getput 各自安全,组合起来仍可能覆盖;即使本地原子,多实例库存仍需共享持久化裁决。
  • 进阶追问:使用 compute 后就能防超卖吗?
  • 进阶回答:只能保证当前进程中该键的复合更新,不能覆盖其他实例、重启和事务失败。
  • 口述答案:我会先用最小交错解释丢失:线程甲和乙都从同一键读到 10,分别计算 11 后写回,容器保证两次读取和两次写入内部结构不损坏,却不会把四个调用自动组成一个事务,最终仍可能是 11 而不是 12。线上取证要固定实例、业务键、输入事件数、每次更新前后值、任务或消息标识以及最终持久化流水;如果单线程准确、并发本地少计,支持复合操作竞态。若每个实例本地都准确但全局库存异常,则要转向跨实例、重复消息或数据库事务假设。止血时停止把内存计数作为扣减依据,保留原始事件并对热点键串行或限流;不能根据当前映射大小直接补库存。修复本地聚合可用 computemerge、原子值或线程私有分片后归并,选择取决于热点和内存预算;全局库存必须由数据库条件更新、唯一业务流水和状态机决定。验证既做多线程固定事件数复算,也做双实例重复投递、进程重启和事务回滚,要求本地聚合不丢、共享库存不为负、同一请求只生效一次。复盘明确哪些容器指标只是观察值,哪些数据能推进业务状态。机制为 E2(既有材料映射),演练输入为 E3(演练设计),真实差异为 E0(待核对)。 停止条件是本地计数与原始事件数无法重算,或任一实例把内存值当成扣减依据;立即切换到持久化流水裁决。恢复验收要在多线程、双实例、重复投递和重启场景下分别证明本地聚合可复算、共享库存不为负、唯一请求只生效一次,再恢复缓存聚合。
  • 追问 1:映射 size() 能否判断任务全部完成? 直接回答:不能,它是容器观察,不是任务状态和副作用完成证明。
  • 追问 2:线程私有聚合有什么成本? 直接回答:会增加键副本和归并成本,需要批次与内存上限。
  • 追问 3:为什么不使用全局锁? 直接回答:无关键被串行且仍只覆盖单进程。
  • 追问 4:本地计数的适用边界是什么? 直接回答:监控、削峰和单实例临时聚合,不能成为跨实例库存账本。
  • 对应唯一正文:并发集合
  1. 问题:IoT(物联网)报警风暴中计数少了、通知却多了,如何联合排查算法、并发容器和线程池?
  • 考点:原始事实、窗口聚合、原子计数、通知背压。
  • 回答思路:把接收、窗口、通知三层分别守恒,再检查层间幂等和失败恢复。
  • 详细答案:原始报警数、窗口计数和通知数语义不同;少计可能是复合更新,通知多可能是重复投递或窗口竞争。
  • 进阶追问:把所有报警都串行处理是否最正确?
  • 进阶回答:可保证单点顺序却可能丢失容量,应按设备和规则键分区,并保护高危报警独立预算。
  • 口述答案:我先明确三个权威口径:接入层保存的原始事件是事实,窗口状态描述同一设备、报警码和规则版本在时间段内的聚合,通知账本描述对人的交付;三者数量本来就不应简单相等。现象层按事件键、设备、规则版本、窗口和线程池记录接收、去重、计数、窗口关闭、通知创建与回执。假设一是窗口映射使用分离读改写,多个线程覆盖计数;假设二是窗口边界算法对时间等于结束点的事件归属不一致;假设三是通知任务创建成功但响应丢失,调用方换键重试产生多条通知;假设四是通知线程池满后策略在拉取线程同步执行,确认延迟导致重复投递。止血先保留原始报警,给高危事件独立配额,低危重复只做摘要,暂停无预算重试;不能为降低压力删除原始事实。修复采用稳定事件幂等键、原子窗口更新、明确的左闭右开边界、有界通知池和查证后重试。验证用可复算事件集覆盖边界时刻、重复、乱序和线程池拒绝,要求原始事件无丢失、窗口计数可重算、通知按策略至多一次生效且高危覆盖。复盘记录规则版本和回放能力。机制和方案为 E2(既有材料映射),风暴规模与压缩率是 E3(演练设计),生产效果为 E0(待核对)。 窗口重算的停止条件是规则版本缺失、时间边界未固定或原始事件不完整;此时不按通知数量反推事实。恢复验收要用原始事件重放出窗口计数,逐条关联通知幂等键,并确认高危事件覆盖、低危重复受控和通知池积压下降后,才恢复正常配额。
  • 追问 1:窗口计数少是否能用通知日志补? 直接回答:不能直接补,应从原始事件按相同规则版本重算。
  • 追问 2:时间边界如何定义? 直接回答:明确时区和左闭右开规则,并为迟到事件设计水位或补算。
  • 追问 3:高危报警能否绕过聚合? 直接回答:可走独立路径,但仍需事件幂等、审计和容量保护。
  • 追问 4:通知越少越好吗? 直接回答:不是,目标是抑制重复噪声且不漏关键状态变化。
  • 对应项目串讲:IoT(物联网)报警
  1. 问题:面对“异步任务卡住”这类模糊现象,如何构造 Java(编程语言)并发证据链而不乱猜?
  • 考点:假设树、低侵入取证、线程与业务关联、恢复判定。
  • 回答思路:先限定业务影响,再按线程、池、锁、依赖、账本逐层裁决。
  • 详细答案:卡住可能是排队、锁等待、输入输出、死锁或任务状态错误;单个指标不能直接命名根因。
  • 进阶追问:第一份线程栈应该怎样使用?
  • 进阶回答:作为假设线索并与后续样本比较,不能凭一个瞬时切片下永久性结论。
  • 口述答案:我先把“卡住”改写成可测现象:哪些任务、从何时开始、到达率与完成率、最老年龄、是否仍有新任务成功、是否影响在线交易。然后建立假设树:线程池排队、工作线程阻塞下游、热点锁竞争、等待环死锁、处理器计算热点、ThreadLocal(线程本地变量)污染导致任务路由错误,或任务已完成但账本未推进。低侵入证据先取版本、流量、线程池快照、连接池、下游延迟和任务状态;再连续采集线程栈,把线程名、任务号和业务键关联。大量等待同一锁要找持有者和回边,RUNNABLE(可运行)要结合处理器采样,队列增长要比较到达与完成,不能把状态名称当根因。止血依据证据选择:暂停大任务、隔离慢依赖、限制热点键、停止无效重试,并保留在途任务和原请求号。修复除代码外还包括历史未知态、迟到结果和重复副作用。验证要求技术信号与业务信号共同恢复:完成率持续高于到达率、最老年龄下降、线程与连接池有余量、任务账本收敛、输出文件或外部动作可校验。复盘记录为何监控没提前发现和下一次故障注入。数字缺证据时标 E3(演练设计),根因未裁决时保持 E0(待核对)。 若连续样本无法把任务号、线程栈和业务账本对上,就暂缓根因结论,只做低风险隔离;若完成率下降而到达率不降,先限制入口。恢复验收需覆盖一个完整业务窗口,确认最老任务下降、账本收敛、外部副作用可查证,并记录仍未裁决的假设。
  • 追问 1:先重启能否更快? 直接回答:可作为止血,但至少先保留低成本现场和在途任务,否则恢复后难以裁决。
  • 追问 2:队列不增长是否排除线程池问题? 直接回答:不能,入口可能已被阻塞或任务根本未成功提交。
  • 追问 3:多份线程栈间隔多久? 直接回答:按故障变化速度取数秒级样本,并保留统一时间和实例信息。
  • 追问 4:何时宣布恢复? 直接回答:观察完整业务窗口,技术容量和业务账本都稳定后。
  • 对应分类合同:失败证据路线
  1. 问题:请端到端回答一次“WMS(仓储管理系统)大促后库存接口慢且偶发超卖告警”的 Java(编程语言)并发故障。
  • 考点:热点、锁与线程池、集合边界、库存不变量、事实等级。
  • 回答思路:从业务影响到假设、证据、止血、修复、验证和复盘完整展开。
  • 详细答案:性能慢和超卖告警可能同源也可能并发发生;先用流水确认是否真实超卖,再定位本地竞争和共享裁决缺口。
  • 进阶追问:告警出现负数就能确认超卖吗?
  • 进阶回答:不能,先核对指标口径、库存账本、订单与预占流水;缓存或监控计算也可能错误。
  • 口述答案:我先限定影响:按租户、仓库、SKU(库存单位)、版本和时间窗统计接口尾延迟、成功率、条件更新失败、库存负数告警与真实订单样本,同时冻结“超卖已发生”的结论,先由库存账本裁决。假设分三层:应用层可能用可变组合键或分离读改写导致聚合错误;并发层可能是热点锁、CAS(比较并交换)重试或锁内远程调用占满线程;持久层可能有路径绕过 available >= quantity 条件或重复请求未命中唯一流水。证据包括原始请求与聚合阶段摘要、热点键分布、线程池到达和完成率、连续线程栈中的持锁与等待关系、CAS(比较并交换)失败、数据库影响行数、事务和预占流水。止血先隔离热点 SKU(库存单位)、限制入口与重试、暂停绕过条件的版本,保护非热点仓库;未知请求按原业务键查证,绝不直接补扣。修复采用不可变键和原子复合操作,缩短临界区并把通知移出锁,线程池有界隔离;最终库存仍由数据库条件更新、唯一幂等流水和合法状态迁移保证。验证在双实例下注入热点、重复、线程暂停和响应丢失,要求库存守恒、重复流水为零、完成率稳定、积压下降且尾延迟达标。复盘增加热点容量、代码门禁和对账演练。简历或源码可证部分才标 E1(直接证据),既有方案为 E2(既有材料映射),压测量级为 E3(演练设计),真实收益缺记录则是 E0(待核对)。 端到端停止条件是库存账本与订单、预占流水无法闭合,或条件更新影响行数与成功响应不一致;先冻结热点和未知请求。恢复验收按仓库与 SKU(库存单位)逐键对账,确认库存不为负、重复流水为零、尾延迟和积压稳定,再逐档解除限流;告警指标恢复不能替代业务守恒。
  • 追问 1:本地锁为什么不是最终方案? 直接回答:它只覆盖一个进程,跨实例仍可同时修改共享库存。
  • 追问 2:扩大线程池能缓解吗? 直接回答:热点串行或慢依赖下会增加等待和冲突,需先确认瓶颈。
  • 追问 3:条件更新 0 行都是库存不足吗? 直接回答:不是,还可能是版本过期、状态关闭或记录不存在。
  • 追问 4:修复后如何处理历史差异? 直接回答:按订单、预占、释放和库存流水生成补偿清单,人工确认不可逆项。
  • 对应项目串讲:WMS(仓储管理系统)库存

8. 复习清单

  • 能先描述现象和业务影响,再提出可证伪假设。
  • 能用线程栈、指标、日志、任务账本和业务流水形成证据链。
  • 能区分止血、根因修复、历史数据恢复和恢复验收。
  • 能说明本地并发安全不等于跨实例业务一致。
  • 能把无证据量级标为 E3(演练设计),把待核对生产事实标为 E0(待核对)。
  • 能回链 091014 唯一正文,不在题库复制机制长文。