[{"data":1,"prerenderedAt":7614},["ShallowReactive",2],{"java:redis-core-interview\u002F":3,"java-wiki-navigation":578},{"id":4,"title":5,"body":6,"commentId":564,"description":565,"difficulty":566,"draft":567,"extension":568,"meta":569,"navigation":570,"order":571,"path":572,"section":573,"seo":574,"stem":575,"updated":576,"__hash__":577},"java\u002Fjava\u002Fredis-core-interview.md","Redis 核心原理：缓存、持久化与集群",{"type":7,"value":8,"toc":529},"minimark",[9,14,18,40,43,47,52,55,59,62,66,69,73,76,80,83,87,90,94,150,152,155,158,160,163,166,168,171,179,183,186,197,200,203,217,220,224,227,248,254,257,260,266,269,272,275,279,282,288,291,334,343,347,351,354,358,361,381,384,387,390,394,397,408,412,415,421,436,439,445,448,452,455,461,464,467,481,484,488,494,497],[10,11,13],"h2",{"id":12},"一redis-为什么快","一、Redis 为什么快",[15,16,17],"p",{},"Redis 的高性能来自多项设计共同作用：",[19,20,21,25,28,31,34,37],"ul",{},[22,23,24],"li",{},"数据主要在内存中访问；",[22,26,27],{},"命令执行路径短，大多数操作复杂度低；",[22,29,30],{},"核心命令串行执行，避免大量锁竞争和上下文切换；",[22,32,33],{},"I\u002FO 多路复用可管理大量连接；",[22,35,36],{},"SDS、dict、listpack、quicklist、skiplist 等结构针对使用场景优化；",[22,38,39],{},"RESP 协议简单。",[15,41,42],{},"“Redis 是单线程”不够准确。核心命令执行长期以单线程模型为主，但持久化、异步释放、复制等工作会使用后台线程或进程；Redis 6 以后还可使用 I\u002FO 线程处理部分网络读写。",[10,44,46],{"id":45},"二常用数据类型与选型","二、常用数据类型与选型",[48,49,51],"h3",{"id":50},"string","String",[15,53,54],{},"用于普通缓存、计数器、Token、开关和分布式锁。底层字符串使用 SDS，支持二进制安全和高效长度获取。",[48,56,58],{"id":57},"hash","Hash",[15,60,61],{},"适合按字段更新对象，但字段过多会形成大 Key。对象很小、整体读取频繁时，序列化后的 String 也可能更简单。",[48,63,65],{"id":64},"list","List",[15,67,68],{},"适合双端队列和有限长度列表。简单队列可以使用 List，但需要消费组、ACK、回溯时更适合 Stream 或专业 MQ。",[48,70,72],{"id":71},"set","Set",[15,74,75],{},"适合去重、集合关系、标签和成员判断。",[48,77,79],{"id":78},"zset","ZSet",[15,81,82],{},"元素唯一并携带 score，适合排行榜、TopN 和按时间排序。小数据量可使用紧凑结构，规模扩大后通常由跳表和字典共同支持排序与定位。",[48,84,86],{"id":85},"stream","Stream",[15,88,89],{},"支持消息 ID、消费组、ACK 和待处理消息列表，可做轻量消息流。但跨系统可靠消息、长期堆积和复杂治理通常仍应选择 Kafka、RocketMQ 等专业 MQ。",[10,91,93],{"id":92},"三穿透击穿与雪崩","三、穿透、击穿与雪崩",[95,96,97,113],"table",{},[98,99,100],"thead",{},[101,102,103,107,110],"tr",{},[104,105,106],"th",{},"问题",[104,108,109],{},"核心特征",[104,111,112],{},"主要方案",[114,115,116,128,139],"tbody",{},[101,117,118,122,125],{},[119,120,121],"td",{},"缓存穿透",[119,123,124],{},"缓存和数据库都不存在",[119,126,127],{},"参数校验、布隆过滤器、空值缓存、限流风控",[101,129,130,133,136],{},[119,131,132],{},"缓存击穿",[119,134,135],{},"单个热点 Key 失效",[119,137,138],{},"逻辑过期、互斥重建、提前刷新、降级",[101,140,141,144,147],{},[119,142,143],{},"缓存雪崩",[119,145,146],{},"大量 Key 集中过期或 Redis 整体不可用",[119,148,149],{},"TTL 随机化、多级缓存、预热、高可用、限流熔断",[48,151,121],{"id":121},[15,153,154],{},"布隆过滤器判断“不存在”时可以直接拦截；判断“可能存在”仍需查询。它存在误判，不存在漏判。缓存空值要设置较短 TTL，既避免重复回源，也防止随机无效 Key 长期占用内存。",[15,156,157],{},"互斥锁只适合大量请求集中在同一个不存在 Key 的情况；面对大量随机 Key，参数校验、布隆过滤器和风控更有效。",[48,159,132],{"id":132},[15,161,162],{},"高并发热点数据可使用逻辑过期：Key 不做物理删除，Value 中保存逻辑过期时间；过期后先返回旧值，再由一个线程异步重建。",[15,164,165],{},"如果不能接受旧值，可以用本地锁或分布式锁限制同一 Key 的回源并发。拿到锁后必须二次检查缓存，避免等待期间其他线程已经完成回填。",[48,167,143],{"id":143},[15,169,170],{},"雪崩不只有“大量 Key 同时过期”，还包括 Redis 集群整体不可用。治理需要覆盖两个层面：",[19,172,173,176],{},[22,174,175],{},"缓存层：TTL 随机化、异步刷新、逻辑过期、缓存预热、多级缓存；",[22,177,178],{},"系统层：限流、熔断、降级、数据库保护和 Redis 高可用。",[10,180,182],{"id":181},"四缓存与数据库一致性","四、缓存与数据库一致性",[15,184,185],{},"Cache Aside 的常用更新策略是：",[187,188,194],"pre",{"className":189,"code":191,"language":192,"meta":193},[190],"language-text","更新数据库\n  ↓\n删除缓存\n","text","",[195,196,191],"code",{"__ignoreMap":193},[15,198,199],{},"通常删除缓存而不是直接更新，是因为缓存可能是聚合结果，并发更新容易互相覆盖，且部分缓存可能根本不会再次被读取。",[15,201,202],{},"删除失败可通过：",[19,204,205,208,211,214],{},[22,206,207],{},"有限重试；",[22,209,210],{},"投递补偿消息；",[22,212,213],{},"监听 binlog\u002FCDC 删除缓存；",[22,215,216],{},"TTL 最终过期兜底。",[15,218,219],{},"它通常提供最终一致性，而不是强一致性。资金余额等强一致数据不能把缓存当权威数据源，核心约束仍应落在数据库事务、唯一键、状态机或条件更新上。",[10,221,223],{"id":222},"五热-key-如何识别与治理","五、热 Key 如何识别与治理",[15,225,226],{},"热 Key 是访问流量集中，而不是 Value 很大。判断依据包括：",[19,228,229,232,235,238,245],{},[22,230,231],{},"云监控或代理层的单 Key QPS TopN；",[22,233,234],{},"客户端采样统计；",[22,236,237],{},"Cluster 节点 CPU、带宽和延迟明显倾斜；",[22,239,240,241,244],{},"LFU 条件下使用 ",[195,242,243],{},"redis-cli --hotkeys"," 辅助发现；",[22,246,247],{},"结合赛事、秒杀、首页配置等业务提前识别。",[15,249,250,253],{},[195,251,252],{},"MONITOR"," 开销大，不应在线上长期启用。",[48,255,256],{"id":256},"读热点",[15,258,259],{},"优先考虑本地缓存和多级缓存：",[187,261,264],{"className":262,"code":263,"language":192,"meta":193},[190],"Caffeine → Redis → 数据库\n",[195,265,263],{"__ignoreMap":193},[15,267,268],{},"也可以创建多个内容相同的副本 Key，并让它们在 Cluster 中落到不同节点。这个方案适合读多写少、允许短暂最终一致的数据；单机 Redis 中只拆 Key 并不能分散节点 CPU 和带宽。",[48,270,271],{"id":271},"写热点",[15,273,274],{},"不能简单复制后随机写。需要根据业务采用分片计数、本地聚合、消息队列削峰、串行化更新或重新设计数据模型，并明确最终汇总和一致性规则。",[10,276,278],{"id":277},"六大-key-如何识别与治理","六、大 Key 如何识别与治理",[15,280,281],{},"大 Key 指 Value 占用内存过大，或集合元素过多。可通过以下方式识别：",[187,283,286],{"className":284,"code":285,"language":192,"meta":193},[190],"redis-cli --bigkeys\nredis-cli --memkeys\nMEMORY USAGE key\nHLEN \u002F LLEN \u002F SCARD \u002F ZCARD \u002F XLEN\n慢日志与监控平台\n",[195,287,285],{"__ignoreMap":193},[15,289,290],{},"治理原则：",[292,293,294,297,300,314,327],"ol",{},[22,295,296],{},"按时间、用户、租户或哈希分片；",[22,298,299],{},"限制集合最大长度，历史数据归档；",[22,301,302,303,306,307,306,310,313],{},"避免 ",[195,304,305],{},"HGETALL","、",[195,308,309],{},"SMEMBERS",[195,311,312],{},"LRANGE 0 -1","；",[22,315,316,317,306,320,306,323,326],{},"使用 ",[195,318,319],{},"HSCAN",[195,321,322],{},"SSCAN",[195,324,325],{},"ZSCAN"," 渐进遍历，List 分段读取；",[22,328,329,330,333],{},"整 Key 删除优先使用 ",[195,331,332],{},"UNLINK","，并在低峰期分批限速。",[15,335,336,338,339,342],{},[195,337,332],{}," 会先从 Keyspace 摘除 Key，再由后台线程释放 Value 内存。因此 Key 立即不可见，但 ",[195,340,341],{},"used_memory"," 可能不会立刻下降。它减少主线程阻塞，不代表删除没有成本。",[10,344,346],{"id":345},"七持久化","七、持久化",[48,348,350],{"id":349},"rdb","RDB",[15,352,353],{},"RDB 是某个时间点的快照。文件紧凑、恢复快，适合备份；缺点是可能丢失两次快照之间的数据，fork 和写时复制也可能造成内存和延迟压力。",[48,355,357],{"id":356},"aof","AOF",[15,359,360],{},"AOF 记录写命令。常见刷盘策略：",[19,362,363,369,375],{},[22,364,365,368],{},[195,366,367],{},"always","：每次写都刷盘，可靠性高、性能成本大；",[22,370,371,374],{},[195,372,373],{},"everysec","：通常最多承受约一秒窗口的数据丢失，常用折中；",[22,376,377,380],{},[195,378,379],{},"no","：交给操作系统决定刷盘。",[15,382,383],{},"AOF rewrite 会把历史操作压缩为能恢复当前状态的更短命令序列。",[48,385,386],{"id":386},"混合持久化",[15,388,389],{},"重写后的 AOF 前部使用 RDB 格式保存全量快照，后部追加增量命令，兼顾恢复速度与数据安全。",[10,391,393],{"id":392},"八主从哨兵与-cluster","八、主从、哨兵与 Cluster",[15,395,396],{},"更准确的定位是：",[19,398,399,402,405],{},[22,400,401],{},"主从复制：数据冗余和读扩展，不能提升单主写吞吐；",[22,403,404],{},"Sentinel：在主从架构上增加监控、选主和自动故障转移；",[22,406,407],{},"Cluster：通过多个 Master 分片实现容量和写吞吐扩展，同时通过副本提供故障转移。",[48,409,411],{"id":410},"cluster-分片","Cluster 分片",[15,413,414],{},"Redis Cluster 把 Keyspace 划分为 16384 个槽：",[187,416,419],{"className":417,"code":418,"language":192,"meta":193},[190],"slot = CRC16(key) mod 16384\n",[195,420,418],{"__ignoreMap":193},[15,422,423,424,427,428,431,432,435],{},"客户端维护 ",[195,425,426],{},"slot → node"," 路由。",[195,429,430],{},"MOVED"," 表示槽归属已经改变，应更新路由；",[195,433,434],{},"ASK"," 表示槽正在迁移，这次临时访问目标节点。",[15,437,438],{},"Hash Tag 可让多个 Key 落到同一槽：",[187,440,443],{"className":441,"code":442,"language":192,"meta":193},[190],"user:{1001}:name\nuser:{1001}:age\n",[195,444,442],{"__ignoreMap":193},[15,446,447],{},"它有利于多 Key 操作，但过度使用会造成槽位热点。",[10,449,451],{"id":450},"九redis-分布式锁","九、Redis 分布式锁",[15,453,454],{},"基础加锁：",[187,456,459],{"className":457,"code":458,"language":192,"meta":193},[190],"SET lock:key unique-token NX PX 30000\n",[195,460,458],{"__ignoreMap":193},[15,462,463],{},"释放锁必须用 Lua 原子地判断 Token 并删除，避免业务执行超时后误删其他线程新获得的锁。",[15,465,466],{},"Redisson Watchdog 可以在未指定固定租约时自动续期，但仍应考虑：",[19,468,469,472,475,478],{},[22,470,471],{},"JVM 长暂停或网络分区；",[22,473,474],{},"主从异步复制与故障切换窗口；",[22,476,477],{},"锁过期后旧线程继续执行；",[22,479,480],{},"业务操作是否具有幂等和 fencing token 保护。",[15,482,483],{},"Redis 锁不是资金一致性的唯一防线。数据库唯一约束、条件更新、幂等号和状态机仍然必要。",[10,485,487],{"id":486},"十速记结论","十、速记结论",[489,490,491],"blockquote",{},[15,492,493],{},"穿透是查不存在的数据，击穿是一个热点 Key 失效，雪崩是大量 Key 失效或 Redis 整体不可用；热 Key 治理访问集中，大 Key 治理数据体积；主从做读扩展和副本，哨兵做自动故障转移，Cluster 做分片扩展和高可用。",[10,495,496],{"id":496},"参考资料",[19,498,499,508,515,522],{},[22,500,501],{},[502,503,507],"a",{"href":504,"rel":505},"https:\u002F\u002Fredis.io\u002Fdocs\u002Flatest\u002Foperate\u002Foss_and_stack\u002Freference\u002Fcluster-spec\u002F",[506],"nofollow","Redis Cluster 规范",[22,509,510],{},[502,511,514],{"href":512,"rel":513},"https:\u002F\u002Fredis.io\u002Fdocs\u002Flatest\u002Fdevelop\u002Fusing-commands\u002Fkeyspace\u002F",[506],"Redis Keyspace 与 SCAN",[22,516,517],{},[502,518,521],{"href":519,"rel":520},"https:\u002F\u002Fredis.io\u002Fdocs\u002Flatest\u002Fcommands\u002Funlink\u002F",[506],"UNLINK 命令",[22,523,524],{},[502,525,528],{"href":526,"rel":527},"https:\u002F\u002Fredis.io\u002Fdocs\u002Flatest\u002Foperate\u002Foss_and_stack\u002Fmanagement\u002Fpersistence\u002F",[506],"Redis 持久化",{"title":193,"searchDepth":530,"depth":530,"links":531},2,[532,533,542,547,548,552,553,558,561,562,563],{"id":12,"depth":530,"text":13},{"id":45,"depth":530,"text":46,"children":534},[535,537,538,539,540,541],{"id":50,"depth":536,"text":51},3,{"id":57,"depth":536,"text":58},{"id":64,"depth":536,"text":65},{"id":71,"depth":536,"text":72},{"id":78,"depth":536,"text":79},{"id":85,"depth":536,"text":86},{"id":92,"depth":530,"text":93,"children":543},[544,545,546],{"id":121,"depth":536,"text":121},{"id":132,"depth":536,"text":132},{"id":143,"depth":536,"text":143},{"id":181,"depth":530,"text":182},{"id":222,"depth":530,"text":223,"children":549},[550,551],{"id":256,"depth":536,"text":256},{"id":271,"depth":536,"text":271},{"id":277,"depth":530,"text":278},{"id":345,"depth":530,"text":346,"children":554},[555,556,557],{"id":349,"depth":536,"text":350},{"id":356,"depth":536,"text":357},{"id":386,"depth":536,"text":386},{"id":392,"depth":530,"text":393,"children":559},[560],{"id":410,"depth":536,"text":411},{"id":450,"depth":530,"text":451},{"id":486,"depth":530,"text":487},{"id":496,"depth":530,"text":496},"wiki:java:redis-core-interview","系统梳理 Redis 数据结构、缓存三大问题、热 Key\u002F大 Key、持久化、分布式锁和 Cluster。","advanced",false,"md",{},true,61,"\u002Fjava\u002Fredis-core-interview","中间件",{"title":5,"description":565},"java\u002Fredis-core-interview","2026-07-16","IJvtjsBsnAbfnndYul-GC8vGD8pgX0Qe0mklDWoWV-I",[579,667,790,926,1034,1132,1205,1982,3297,4013,4844,5573,6111,6656,7030],{"id":580,"title":581,"body":582,"commentId":656,"description":657,"difficulty":658,"draft":567,"extension":568,"meta":659,"navigation":570,"order":660,"path":661,"section":662,"seo":663,"stem":664,"updated":665,"__hash__":666},"java\u002Fjava\u002Ffundamentals.md","Java 基础知识地图",{"type":7,"value":583,"toc":650},[584,587,590,593,615,618,621,624],[10,585,586],{"id":586},"这一部分解决什么问题",[15,588,589],{},"Java 基础不是 API 的罗列，而是理解类型、对象和运行时行为之间的关系。后续的并发、JVM 和框架知识都建立在这些概念之上。",[10,591,592],{"id":592},"知识路线",[292,594,595,598,603,606,609,612],{},[22,596,597],{},"基本类型、包装类型与自动装箱",[22,599,600,602],{},[195,601,51],{},"、不可变对象与对象比较",[22,604,605],{},"集合框架与常见数据结构",[22,607,608],{},"泛型、类型擦除与边界",[22,610,611],{},"反射、注解与动态代理",[22,613,614],{},"异常体系和资源管理",[10,616,617],{"id":617},"推荐学习方式",[15,619,620],{},"先用一句话回答“它是什么”，再通过小段代码验证边界，最后把它放回 JVM 和框架运行机制中理解。知识库会逐步把每个主题拆成独立条目。",[10,622,623],{"id":623},"面试时容易出现的问题",[19,625,626,633,644,647],{},[22,627,628,629,632],{},"只背诵 ",[195,630,631],{},"HashMap"," 的结论，不理解哈希冲突和扩容成本。",[22,634,635,636,639,640,643],{},"把 ",[195,637,638],{},"=="," 和 ",[195,641,642],{},"equals"," 简化成“地址比较”和“内容比较”。",[22,645,646],{},"知道泛型语法，但无法解释类型擦除带来的限制。",[22,648,649],{},"会使用反射，却不了解它为什么被 Spring 等框架广泛使用。",{"title":193,"searchDepth":530,"depth":530,"links":651},[652,653,654,655],{"id":586,"depth":530,"text":586},{"id":592,"depth":530,"text":592},{"id":617,"depth":530,"text":617},{"id":623,"depth":530,"text":623},"wiki:java:fundamentals","Java 类型系统、集合、泛型、反射、异常与常用语言机制的学习入口。","basic",{},1,"\u002Fjava\u002Ffundamentals","Java 基础",{"title":581,"description":657},"java\u002Ffundamentals","2026-07-10","TZ18cq4bXSr9iscSOTSlgfMfbmTp2H5kOdVxAbnY_2s",{"id":668,"title":669,"body":670,"commentId":781,"description":782,"difficulty":783,"draft":567,"extension":568,"meta":784,"navigation":570,"order":530,"path":785,"section":786,"seo":787,"stem":788,"updated":665,"__hash__":789},"java\u002Fjava\u002Fjvm.md","JVM 知识地图",{"type":7,"value":671,"toc":770},[672,676,679,681,684,698,701,715,718,732,735,749,752,759,762],[10,673,675],{"id":674},"为什么要理解-jvm","为什么要理解 JVM",[15,677,678],{},"JVM 把 Java 源代码和具体硬件隔开，但生产问题最终仍会落到内存、线程、字节码和操作系统资源上。理解 JVM 的目标不是记参数，而是能解释和诊断程序行为。",[10,680,592],{"id":592},[48,682,683],{"id":683},"类加载",[19,685,686,689,692,695],{},[22,687,688],{},"加载、验证、准备、解析和初始化",[22,690,691],{},"双亲委派模型",[22,693,694],{},"类加载器隔离",[22,696,697],{},"静态字段和初始化顺序",[48,699,700],{"id":700},"运行时数据区",[19,702,703,706,709,712],{},[22,704,705],{},"堆与对象分配",[22,707,708],{},"虚拟机栈与栈帧",[22,710,711],{},"方法区与元空间",[22,713,714],{},"直接内存",[48,716,717],{"id":717},"垃圾回收",[19,719,720,723,726,729],{},[22,721,722],{},"可达性分析",[22,724,725],{},"分代收集",[22,727,728],{},"CMS、G1 与现代收集器",[22,730,731],{},"停顿时间、吞吐量和内存占用之间的权衡",[48,733,734],{"id":734},"性能排查",[19,736,737,740,743,746],{},[22,738,739],{},"GC 日志",[22,741,742],{},"线程快照",[22,744,745],{},"堆转储",[22,747,748],{},"CPU、内存和锁竞争定位",[10,750,751],{"id":751},"一句话原则",[15,753,754,755,758],{},"先确定现象属于 CPU、内存、线程还是外部依赖，再选择工具；不要看到 ",[195,756,757],{},"OutOfMemoryError"," 就先调整堆大小。",[10,760,761],{"id":761},"深入阅读",[19,763,764],{},[22,765,766],{},[502,767,769],{"href":768},"\u002Fwiki\u002Fjava\u002Fjvm-core-interview\u002F","JVM 核心原理：内存、GC、类加载与故障排查",{"title":193,"searchDepth":530,"depth":530,"links":771},[772,773,779,780],{"id":674,"depth":530,"text":675},{"id":592,"depth":530,"text":592,"children":774},[775,776,777,778],{"id":683,"depth":536,"text":683},{"id":700,"depth":536,"text":700},{"id":717,"depth":536,"text":717},{"id":734,"depth":536,"text":734},{"id":751,"depth":530,"text":751},{"id":761,"depth":530,"text":761},"wiki:java:jvm","从类加载、运行时内存到垃圾回收与性能排查，理解 Java 程序如何真正运行。","intermediate",{},"\u002Fjava\u002Fjvm","JVM",{"title":669,"description":782},"java\u002Fjvm","wJvcI8-xICG4e3NqdpPrD1ZAvpZGWwpaUskc6uv-WOI",{"id":791,"title":792,"body":793,"commentId":918,"description":919,"difficulty":783,"draft":567,"extension":568,"meta":920,"navigation":570,"order":536,"path":921,"section":922,"seo":923,"stem":924,"updated":665,"__hash__":925},"java\u002Fjava\u002Fconcurrency.md","Java 并发知识地图",{"type":7,"value":794,"toc":908},[795,798,801,808,811,821,824,827,829,863,866,880,882],[10,796,797],{"id":797},"并发问题的三个核心维度",[48,799,800],{"id":800},"可见性",[15,802,803,804,807],{},"一个线程修改的数据，何时能够被另一个线程观察到。",[195,805,806],{},"volatile","、锁和线程启动\u002F终止规则都与 happens-before 有关。",[48,809,810],{"id":810},"原子性",[15,812,813,814,817,818,820],{},"一个操作能否被其他线程观察到中间状态。",[195,815,816],{},"count++"," 即使使用 ",[195,819,806],{}," 也不是原子操作。",[48,822,823],{"id":823},"有序性",[15,825,826],{},"编译器和处理器可以在不改变单线程结果的前提下重排序。并发程序必须通过同步规则建立必要顺序。",[10,828,592],{"id":592},[292,830,831,834,839,845,848,851,854,860],{},[22,832,833],{},"Java 内存模型与 happens-before",[22,835,836,838],{},[195,837,806],{}," 与内存屏障",[22,840,841,844],{},[195,842,843],{},"synchronized"," 与对象监视器",[22,846,847],{},"CAS、原子类和 ABA 问题",[22,849,850],{},"AQS、锁和同步器",[22,852,853],{},"线程池与任务调度",[22,855,856,859],{},[195,857,858],{},"CompletableFuture"," 与异步编排",[22,861,862],{},"并发容器",[10,864,865],{"id":865},"生产实践原则",[19,867,868,871,874,877],{},[22,869,870],{},"尽量减少共享可变状态。",[22,872,873],{},"明确线程池的职责、队列容量和拒绝策略。",[22,875,876],{},"不要把耗时不可控的任务提交到公共线程池。",[22,878,879],{},"监控活跃线程数、队列长度、拒绝次数和任务耗时。",[10,881,761],{"id":761},[19,883,884,890,896,902],{},[22,885,886],{},[502,887,889],{"href":888},"\u002Fwiki\u002Fjava\u002Fjava-memory-model\u002F","Java 内存模型：可见性、有序性与安全发布",[22,891,892],{},[502,893,895],{"href":894},"\u002Fwiki\u002Fjava\u002Fcas-atomic-operations\u002F","CAS 与原子操作：原理、ABA 与 LongAdder",[22,897,898],{},[502,899,901],{"href":900},"\u002Fwiki\u002Fjava\u002Fjava-lock-mechanisms\u002F","Java 锁机制：synchronized、Lock 与读写锁",[22,903,904],{},[502,905,907],{"href":906},"\u002Fwiki\u002Fjava\u002Faqs-internals\u002F","AQS 源码原理：state、同步队列与 Condition",{"title":193,"searchDepth":530,"depth":530,"links":909},[910,915,916,917],{"id":797,"depth":530,"text":797,"children":911},[912,913,914],{"id":800,"depth":536,"text":800},{"id":810,"depth":536,"text":810},{"id":823,"depth":536,"text":823},{"id":592,"depth":530,"text":592},{"id":865,"depth":530,"text":865},{"id":761,"depth":530,"text":761},"wiki:java:concurrency","理解可见性、原子性、有序性，以及 Java 并发工具如何建立在这些规则之上。",{},"\u002Fjava\u002Fconcurrency","并发编程",{"title":792,"description":919},"java\u002Fconcurrency","4JJKGhweluAd5xErTxIwF4Qm04wYCAC3gifkrHsgzy0",{"id":927,"title":928,"body":929,"commentId":1025,"description":1026,"difficulty":783,"draft":567,"extension":568,"meta":1027,"navigation":570,"order":1028,"path":1029,"section":1030,"seo":1031,"stem":1032,"updated":665,"__hash__":1033},"java\u002Fjava\u002Fspring.md","Spring 知识地图",{"type":7,"value":930,"toc":1017},[931,935,938,942,959,963,977,980,997,1000,1007,1009],[10,932,934],{"id":933},"理解-spring-的主线","理解 Spring 的主线",[15,936,937],{},"Spring 的核心不是注解数量，而是容器如何创建对象、建立依赖，并在合适的扩展点改变对象行为。",[10,939,941],{"id":940},"ioc-容器","IOC 容器",[19,943,944,947,950,953,956],{},[22,945,946],{},"BeanDefinition 的来源",[22,948,949],{},"Bean 创建生命周期",[22,951,952],{},"依赖注入",[22,954,955],{},"BeanPostProcessor",[22,957,958],{},"循环依赖",[10,960,962],{"id":961},"aop","AOP",[19,964,965,968,971,974],{},[22,966,967],{},"JDK 动态代理和 CGLIB",[22,969,970],{},"切点与通知",[22,972,973],{},"代理对象调用边界",[22,975,976],{},"自调用为什么可能导致切面失效",[10,978,979],{"id":979},"事务",[19,981,982,985,988,991,994],{},[22,983,984],{},"事务管理器",[22,986,987],{},"传播行为",[22,989,990],{},"隔离级别",[22,992,993],{},"回滚规则",[22,995,996],{},"声明式事务失效场景",[10,998,999],{"id":999},"学习建议",[15,1001,1002,1003,1006],{},"选择一条最短链路，从 ",[195,1004,1005],{},"ApplicationContext"," 启动到一个带事务的 Service 被调用，跟踪其中的对象创建、代理生成和拦截过程。这样比孤立背诵源码类名更有效。",[10,1008,761],{"id":761},[19,1010,1011],{},[22,1012,1013],{},[502,1014,1016],{"href":1015},"\u002Fwiki\u002Fjava\u002Fspring-source-code\u002F","Spring 源码主线：IoC、AOP 与事务",{"title":193,"searchDepth":530,"depth":530,"links":1018},[1019,1020,1021,1022,1023,1024],{"id":933,"depth":530,"text":934},{"id":940,"depth":530,"text":941},{"id":961,"depth":530,"text":962},{"id":979,"depth":530,"text":979},{"id":999,"depth":530,"text":999},{"id":761,"depth":530,"text":761},"wiki:java:spring","从 IOC、AOP 和事务出发，理解 Spring 如何组织和增强应用程序。",{},4,"\u002Fjava\u002Fspring","Spring",{"title":928,"description":1026},"java\u002Fspring","Zhy_5PsTvk2CvuvADdYZYCncMt1-NM2aOHuPKuKZLU4",{"id":1035,"title":1036,"body":1037,"commentId":1123,"description":1124,"difficulty":783,"draft":567,"extension":568,"meta":1125,"navigation":570,"order":1126,"path":1127,"section":1128,"seo":1129,"stem":1130,"updated":665,"__hash__":1131},"java\u002Fjava\u002Fdatabase.md","数据库与缓存知识地图",{"type":7,"value":1038,"toc":1116},[1039,1042,1045,1049,1069,1072,1089,1092,1095,1097],[10,1040,1041],{"id":1041},"数据库知识的核心问题",[15,1043,1044],{},"后端开发关注的不只是 SQL 能否执行，还要理解数据如何被定位、并发修改如何协调、失败后如何恢复，以及缓存加入后怎样维持可接受的一致性。",[10,1046,1048],{"id":1047},"mysql","MySQL",[19,1050,1051,1054,1057,1060,1063,1066],{},[22,1052,1053],{},"B+Tree 与索引组织",[22,1055,1056],{},"联合索引和最左匹配",[22,1058,1059],{},"执行计划",[22,1061,1062],{},"MVCC 与 Read View",[22,1064,1065],{},"行锁、间隙锁和死锁",[22,1067,1068],{},"redo log、undo log 与 binlog",[10,1070,1071],{"id":1071},"缓存",[19,1073,1074,1077,1080,1083,1086],{},[22,1075,1076],{},"Cache Aside",[22,1078,1079],{},"缓存穿透、击穿和雪崩",[22,1081,1082],{},"热点 Key",[22,1084,1085],{},"数据更新与失效",[22,1087,1088],{},"最终一致性",[10,1090,1091],{"id":1091},"排查顺序",[15,1093,1094],{},"面对慢查询，先确认实际执行 SQL 和参数，再看执行计划、扫描行数、锁等待和数据分布。不要只根据 SQL 外观判断是否使用索引。",[10,1096,761],{"id":761},[19,1098,1099,1105,1110],{},[22,1100,1101],{},[502,1102,1104],{"href":1103},"\u002Fwiki\u002Fjava\u002Fmysql-core-interview\u002F","MySQL 核心原理：索引、事务、锁与性能排查",[22,1106,1107],{},[502,1108,5],{"href":1109},"\u002Fwiki\u002Fjava\u002Fredis-core-interview\u002F",[22,1111,1112],{},[502,1113,1115],{"href":1114},"\u002Fwiki\u002Fjava\u002Fkafka-mq-reliability\u002F","Kafka \u002F MQ 核心原理：可靠性、幂等与积压治理",{"title":193,"searchDepth":530,"depth":530,"links":1117},[1118,1119,1120,1121,1122],{"id":1041,"depth":530,"text":1041},{"id":1047,"depth":530,"text":1048},{"id":1071,"depth":530,"text":1071},{"id":1091,"depth":530,"text":1091},{"id":761,"depth":530,"text":761},"wiki:java:database","围绕索引、事务、锁、缓存与数据一致性，建立后端开发需要的数据库认知。",{},5,"\u002Fjava\u002Fdatabase","数据库",{"title":1036,"description":1124},"java\u002Fdatabase","X_JauvYGva6QDVw38eu2HNscqbcvJw4jjqGQajxExwY",{"id":1133,"title":1134,"body":1135,"commentId":1196,"description":1197,"difficulty":658,"draft":567,"extension":568,"meta":1198,"navigation":570,"order":1199,"path":1200,"section":1201,"seo":1202,"stem":1203,"updated":665,"__hash__":1204},"java\u002Fjava\u002Finterview.md","Java 面试专题使用说明",{"type":7,"value":1136,"toc":1191},[1137,1140,1143,1146,1179,1182,1188],[10,1138,1139],{"id":1139},"这里不会做什么",[15,1141,1142],{},"不会把数百道问题堆在一个页面，也不会只给出适合背诵但无法解释的标准答案。",[10,1144,1145],{"id":1145},"每个问题的组织方式",[292,1147,1148,1155,1161,1167,1173],{},[22,1149,1150,1154],{},[1151,1152,1153],"strong",{},"一句话回答","：先准确回答问题。",[22,1156,1157,1160],{},[1151,1158,1159],{},"核心原理","：说明这个结论为什么成立。",[22,1162,1163,1166],{},[1151,1164,1165],{},"代码验证","：使用尽可能小的例子验证边界。",[22,1168,1169,1172],{},[1151,1170,1171],{},"常见误区","：指出容易混淆的地方。",[22,1174,1175,1178],{},[1151,1176,1177],{},"继续追问","：把当前问题连接到更深层知识。",[10,1180,1181],{"id":1181},"示例追问链",[187,1183,1186],{"className":1184,"code":1185,"language":192,"meta":193},[190],"HashMap 为什么线程不安全？\n  → put 操作包含哪些步骤？\n  → 扩容时发生什么？\n  → ConcurrentHashMap 如何降低竞争？\n  → JDK 不同版本的实现有什么变化？\n",[195,1187,1185],{"__ignoreMap":193},[15,1189,1190],{},"面试题的价值不是预测题目，而是帮助自己检查知识之间是否已经形成连接。",{"title":193,"searchDepth":530,"depth":530,"links":1192},[1193,1194,1195],{"id":1139,"depth":530,"text":1139},{"id":1145,"depth":530,"text":1145},{"id":1181,"depth":530,"text":1181},"wiki:java:interview","用简明回答、原理解释、代码验证和追问路径组织 Java 面试知识。",{},6,"\u002Fjava\u002Finterview","面试专题",{"title":1134,"description":1197},"java\u002Finterview","AWxAYmdi1IahbXu7QwVZPfsiCXbVU4YTMMoshQkzEl8",{"id":1206,"title":769,"body":1207,"commentId":1974,"description":1975,"difficulty":566,"draft":567,"extension":568,"meta":1976,"navigation":570,"order":1977,"path":1978,"section":786,"seo":1979,"stem":1980,"updated":576,"__hash__":1981},"java\u002Fjava\u002Fjvm-core-interview.md",{"type":7,"value":1208,"toc":1926},[1209,1213,1216,1286,1288,1295,1298,1303,1307,1310,1313,1337,1340,1346,1352,1356,1359,1365,1369,1372,1375,1378,1381,1392,1395,1399,1402,1419,1422,1425,1439,1443,1446,1457,1460,1463,1487,1491,1495,1498,1502,1505,1509,1512,1515,1519,1523,1526,1530,1533,1536,1556,1560,1563,1567,1570,1574,1577,1583,1586,1593,1602,1613,1616,1619,1630,1633,1636,1639,1650,1653,1657,1660,1666,1669,1672,1707,1714,1718,1722,1725,1729,1732,1736,1739,1743,1750,1754,1757,1761,1765,1790,1793,1813,1817,1820,1843,1846,1850,1881,1885,1890,1892,1922],[10,1210,1212],{"id":1211},"一jvm-运行时数据区","一、JVM 运行时数据区",[15,1214,1215],{},"JVM 规范关注逻辑区域，HotSpot 决定具体实现。常见区域包括：",[95,1217,1218,1232],{},[98,1219,1220],{},[101,1221,1222,1225,1229],{},[104,1223,1224],{},"区域",[104,1226,1228],{"align":1227},"right","是否线程共享",[104,1230,1231],{},"主要内容",[114,1233,1234,1245,1255,1265,1276],{},[101,1235,1236,1239,1242],{},[119,1237,1238],{},"程序计数器",[119,1240,1241],{"align":1227},"否",[119,1243,1244],{},"当前线程执行位置",[101,1246,1247,1250,1252],{},[119,1248,1249],{},"虚拟机栈",[119,1251,1241],{"align":1227},[119,1253,1254],{},"栈帧、局部变量表、操作数栈、返回信息",[101,1256,1257,1260,1262],{},[119,1258,1259],{},"本地方法栈",[119,1261,1241],{"align":1227},[119,1263,1264],{},"Native 方法调用",[101,1266,1267,1270,1273],{},[119,1268,1269],{},"堆",[119,1271,1272],{"align":1227},"是",[119,1274,1275],{},"对象和数组，是 GC 主要管理区域",[101,1277,1278,1281,1283],{},[119,1279,1280],{},"方法区",[119,1282,1272],{"align":1227},[119,1284,1285],{},"类元数据、运行时常量池、方法信息等规范概念",[48,1287,711],{"id":711},[15,1289,1290,1291,1294],{},"方法区是 JVM 规范定义的逻辑区域；Metaspace 是 HotSpot 对类元数据存储的一种实现。JDK 8 起永久代被移除，类元数据主要放在本地内存中的 Metaspace，而不是受 ",[195,1292,1293],{},"-Xmx"," 直接限制。",[15,1296,1297],{},"因此：",[489,1299,1300],{},[15,1301,1302],{},"方法区不等于永久代，也不等于元空间；后两者是不同历史阶段的 HotSpot 实现。",[10,1304,1306],{"id":1305},"二运行时常量池与字符串常量池","二、运行时常量池与字符串常量池",[15,1308,1309],{},"Class 文件常量池保存字面量和符号引用。类加载后，对应信息进入每个类的运行时常量池。",[15,1311,1312],{},"字符串常量池用于复用字符串对象，在现代 HotSpot 中字符串对象本身位于 Java 堆。不要把“运行时常量池”和“字符串常量池”当成同一个东西。",[187,1314,1318],{"className":1315,"code":1316,"language":1317,"meta":193,"style":193},"language-java shiki shiki-themes github-light github-dark","String a = \"abc\";\nString b = \"abc\";\nString c = new String(\"abc\");\n","java",[195,1319,1320,1327,1332],{"__ignoreMap":193},[1321,1322,1324],"span",{"class":1323,"line":660},"line",[1321,1325,1326],{},"String a = \"abc\";\n",[1321,1328,1329],{"class":1323,"line":530},[1321,1330,1331],{},"String b = \"abc\";\n",[1321,1333,1334],{"class":1323,"line":536},[1321,1335,1336],{},"String c = new String(\"abc\");\n",[15,1338,1339],{},"通常：",[187,1341,1344],{"className":1342,"code":1343,"language":192,"meta":193},[190],"a == b  \u002F\u002F true，引用同一池中字符串\na == c  \u002F\u002F false，c 是显式创建的新对象\na.equals(c) \u002F\u002F true，内容相同\n",[195,1345,1343],{"__ignoreMap":193},[15,1347,1348,1351],{},[195,1349,1350],{},"new String(\"abc\")"," 涉及池中字面量对象和新建 String 对象，但“总共创建几个对象”取决于池中字符串是否此前已经存在，不能脱离上下文固定回答。",[10,1353,1355],{"id":1354},"三对象是如何创建的","三、对象是如何创建的",[15,1357,1358],{},"典型过程：",[187,1360,1363],{"className":1361,"code":1362,"language":192,"meta":193},[190],"检查类是否已加载\n  ↓\n为对象分配内存\n  ↓\n把实例字段初始化为零值\n  ↓\n设置对象头\n  ↓\n执行构造方法\n",[195,1364,1362],{"__ignoreMap":193},[48,1366,1368],{"id":1367},"tlab","TLAB",[15,1370,1371],{},"多线程在共享堆上分配对象会产生竞争。HotSpot 可给线程在 Eden 中分配 TLAB，线程在自己的缓冲区内通过指针碰撞快速分配；空间不足时再申请新 TLAB 或走慢路径。",[15,1373,1374],{},"TLAB 解决的是分配竞争，不代表对象属于线程私有。对象仍位于堆中，并可能被其他线程引用。",[48,1376,1377],{"id":1377},"对象布局",[15,1379,1380],{},"典型对象由以下部分组成：",[19,1382,1383,1386,1389],{},[22,1384,1385],{},"对象头：Mark Word、类型指针等；",[22,1387,1388],{},"实例数据；",[22,1390,1391],{},"对齐填充。",[15,1393,1394],{},"对象大小会受到压缩类指针、字段排列和对齐规则影响，不能只把字段字节数简单相加。",[10,1396,1398],{"id":1397},"四可达性分析与-gc-roots","四、可达性分析与 GC Roots",[15,1400,1401],{},"JVM 从 GC Roots 出发遍历引用图，无法到达的对象才可能被回收。常见 Root 包括：",[19,1403,1404,1407,1410,1413,1416],{},[22,1405,1406],{},"栈帧局部变量引用；",[22,1408,1409],{},"已加载类的静态字段引用；",[22,1411,1412],{},"JNI 引用；",[22,1414,1415],{},"活跃线程及 JVM 内部引用；",[22,1417,1418],{},"同步监视器持有的对象等。",[15,1420,1421],{},"引用计数不能处理循环引用，因此主流 JVM 使用可达性分析。",[48,1423,1424],{"id":1424},"四种引用",[19,1426,1427,1430,1433,1436],{},[22,1428,1429],{},"强引用：只要可达就不会回收；",[22,1431,1432],{},"软引用：内存压力下可能回收，不适合现代缓存的精确容量管理；",[22,1434,1435],{},"弱引用：发生 GC 时容易被回收，常用于不阻止对象存活的关联关系；",[22,1437,1438],{},"虚引用：不提供普通对象访问，配合 ReferenceQueue 感知回收与管理堆外资源。",[10,1440,1442],{"id":1441},"五分代与垃圾回收术语","五、分代与垃圾回收术语",[15,1444,1445],{},"分代假设认为大多数对象朝生夕死，少量对象长期存活。传统分代堆通常包括 Eden、Survivor 和 Old。",[19,1447,1448,1451,1454],{},[22,1449,1450],{},"Young GC \u002F Minor GC：主要回收年轻代；",[22,1452,1453],{},"Major GC：语义在不同资料和收集器中不完全统一，通常指老年代相关收集；",[22,1455,1456],{},"Full GC：通常涉及整个堆以及类卸载等更广范围工作。",[15,1458,1459],{},"面试时不要把 Major GC 和 Full GC 永远画等号，应说明术语依赖具体收集器和日志。",[48,1461,1462],{"id":1462},"常见触发因素",[19,1464,1465,1468,1471,1474,1477,1484],{},[22,1466,1467],{},"Eden 分配失败触发年轻代收集；",[22,1469,1470],{},"老年代空间或晋升担保不足；",[22,1472,1473],{},"大对象、Humongous Object 分配压力；",[22,1475,1476],{},"元空间达到阈值，尝试类卸载；",[22,1478,1479,1480,1483],{},"显式 ",[195,1481,1482],{},"System.gc()"," 请求；",[22,1485,1486],{},"收集器并发周期跟不上分配速度。",[10,1488,1490],{"id":1489},"六垃圾回收算法","六、垃圾回收算法",[48,1492,1494],{"id":1493},"标记清除","标记—清除",[15,1496,1497],{},"标记存活对象后清理垃圾，速度直接，但会产生碎片。",[48,1499,1501],{"id":1500},"标记复制","标记—复制",[15,1503,1504],{},"把存活对象复制到另一块区域，回收后空间连续，适合存活率低的年轻代；代价是复制和额外空间。",[48,1506,1508],{"id":1507},"标记整理","标记—整理",[15,1510,1511],{},"标记后把存活对象向一端移动，减少碎片，适合存活率较高区域，但移动成本更高。",[15,1513,1514],{},"现代收集器往往组合使用，不应把一个收集器简单等同于一种算法。",[10,1516,1518],{"id":1517},"七常见收集器","七、常见收集器",[48,1520,1522],{"id":1521},"serial-parallel","Serial \u002F Parallel",[15,1524,1525],{},"Serial 结构简单，适合小堆或资源受限场景。Parallel 以吞吐优先，使用多线程完成 Stop-The-World 收集。",[48,1527,1529],{"id":1528},"g1","G1",[15,1531,1532],{},"G1 把堆划分为多个 Region，年轻代和老年代是 Region 的逻辑集合。它通过并发标记、转移存活对象和优先回收垃圾收益高的 Region，努力满足停顿目标。",[15,1534,1535],{},"需要关注：",[19,1537,1538,1541,1544,1547,1550],{},[22,1539,1540],{},"Young GC 与 Mixed GC；",[22,1542,1543],{},"Humongous Object；",[22,1545,1546],{},"Remembered Set 与跨 Region 引用；",[22,1548,1549],{},"并发标记周期是否能跟上分配；",[22,1551,1552,1555],{},[195,1553,1554],{},"MaxGCPauseMillis"," 是目标，不是硬保证。",[48,1557,1559],{"id":1558},"zgc","ZGC",[15,1561,1562],{},"ZGC 面向低延迟，绝大部分工作与应用并发执行，停顿通常与堆大小弱相关。低停顿不是“零成本”，会消耗额外 CPU、内存和吞吐。",[48,1564,1566],{"id":1565},"cms","CMS",[15,1568,1569],{},"CMS 是历史上常见的低停顿老年代收集器，存在内存碎片、并发失败等问题，已从现代 JDK 中移除。面试可以讲原理和历史影响，但不要把它当作当前默认选择。",[10,1571,1573],{"id":1572},"八类加载机制","八、类加载机制",[15,1575,1576],{},"类的生命周期通常包括：",[187,1578,1581],{"className":1579,"code":1580,"language":192,"meta":193},[190],"加载\n→ 验证\n→ 准备\n→ 解析\n→ 初始化\n→ 使用\n→ 卸载\n",[195,1582,1580],{"__ignoreMap":193},[48,1584,1585],{"id":1585},"准备与初始化区别",[15,1587,1588,1589,1592],{},"准备阶段为静态字段分配存储并设置默认值；初始化阶段执行 ",[195,1590,1591],{},"\u003Cclinit>","，应用源代码中的静态字段赋值和静态代码块在这里生效。",[187,1594,1596],{"className":1315,"code":1595,"language":1317,"meta":193,"style":193},"static int value = 10;\n",[195,1597,1598],{"__ignoreMap":193},[1321,1599,1600],{"class":1323,"line":660},[1321,1601,1595],{},[15,1603,1604,1605,1608,1609,1612],{},"准备阶段通常先得到 ",[195,1606,1607],{},"0","，初始化阶段再赋为 ",[195,1610,1611],{},"10","。编译期常量的处理可能有所不同。",[48,1614,1615],{"id":1615},"双亲委派",[15,1617,1618],{},"类加载器先把加载请求委托给父加载器，父加载器无法完成时再由自己尝试。价值包括：",[19,1620,1621,1624,1627],{},[22,1622,1623],{},"避免核心类重复加载；",[22,1625,1626],{},"保证类身份和安全边界；",[22,1628,1629],{},"提高基础类复用。",[15,1631,1632],{},"SPI、模块化容器、热部署等场景可能使用线程上下文类加载器或自定义策略，但这是有控制地改变委派路径，不等于双亲委派完全失效。",[48,1634,1635],{"id":1635},"类什么时候能卸载",[15,1637,1638],{},"通常需要同时满足：",[19,1640,1641,1644,1647],{},[22,1642,1643],{},"该类的所有实例不可达；",[22,1645,1646],{},"对应 Class 对象不可达；",[22,1648,1649],{},"定义它的 ClassLoader 不可达。",[15,1651,1652],{},"频繁动态生成类、CGLIB 代理或脚本类却让 ClassLoader 长期存活，可能导致 Metaspace 增长。",[10,1654,1656],{"id":1655},"九threadlocal-为什么会内存泄漏","九、ThreadLocal 为什么会内存泄漏",[15,1658,1659],{},"ThreadLocalMap 的 Entry 对 ThreadLocal Key 是弱引用，对 Value 是强引用：",[187,1661,1664],{"className":1662,"code":1663,"language":192,"meta":193},[190],"Thread → ThreadLocalMap → Entry → Value\n                         ↘ weak Key\n",[195,1665,1663],{"__ignoreMap":193},[15,1667,1668],{},"如果 ThreadLocal 对象被回收，Key 可能变成 null，但只要线程长期存活，Value 仍可能通过线程链路被强引用。线程池线程尤其容易长期存在。",[15,1670,1671],{},"规范用法：",[187,1673,1675],{"className":1315,"code":1674,"language":1317,"meta":193,"style":193},"try {\n    threadLocal.set(value);\n    \u002F\u002F 使用\n} finally {\n    threadLocal.remove();\n}\n",[195,1676,1677,1682,1687,1692,1697,1702],{"__ignoreMap":193},[1321,1678,1679],{"class":1323,"line":660},[1321,1680,1681],{},"try {\n",[1321,1683,1684],{"class":1323,"line":530},[1321,1685,1686],{},"    threadLocal.set(value);\n",[1321,1688,1689],{"class":1323,"line":536},[1321,1690,1691],{},"    \u002F\u002F 使用\n",[1321,1693,1694],{"class":1323,"line":1028},[1321,1695,1696],{},"} finally {\n",[1321,1698,1699],{"class":1323,"line":1126},[1321,1700,1701],{},"    threadLocal.remove();\n",[1321,1703,1704],{"class":1323,"line":1199},[1321,1705,1706],{},"}\n",[15,1708,1709,1710,1713],{},"ThreadLocalMap 会在部分操作时清理陈旧 Entry，但不能依赖这种机会式清理替代 ",[195,1711,1712],{},"remove()","。",[10,1715,1717],{"id":1716},"十常见-oom-与定位思路","十、常见 OOM 与定位思路",[48,1719,1721],{"id":1720},"java-heap-space","Java heap space",[15,1723,1724],{},"可能是内存泄漏，也可能只是堆容量不足或流量突增。看对象数量、保留路径、增长趋势和 GC 后存活量。",[48,1726,1728],{"id":1727},"gc-overhead-limit-exceeded","GC overhead limit exceeded",[15,1730,1731],{},"大量时间用于 GC，却只能回收很少空间。通常说明存活集过大、堆过小或存在泄漏。",[48,1733,1735],{"id":1734},"metaspace","Metaspace",[15,1737,1738],{},"关注动态类生成、ClassLoader 数量和卸载情况。普通 Bean 属性复制主要创建堆对象，并不会因为“拷贝字段”直接占用大量 Metaspace；只有持续生成和加载新类才会明显推动类元数据增长。",[48,1740,1742],{"id":1741},"direct-buffer-memory","Direct buffer memory",[15,1744,1745,1746,1749],{},"关注 NIO DirectByteBuffer、网络框架池化内存、显式清理和 ",[195,1747,1748],{},"MaxDirectMemorySize","。堆使用正常不代表进程没有内存压力。",[48,1751,1753],{"id":1752},"unable-to-create-native-thread","unable to create native thread",[15,1755,1756],{},"可能是线程数过多、单线程栈过大、进程限制或系统内存不足。此时调大堆反而可能进一步挤压本地内存。",[10,1758,1760],{"id":1759},"十一线上排障流程","十一、线上排障流程",[48,1762,1764],{"id":1763},"cpu-高","CPU 高",[292,1766,1767,1770,1773,1784,1787],{},[22,1768,1769],{},"系统工具定位高 CPU 线程；",[22,1771,1772],{},"将线程 ID 转为十六进制；",[22,1774,1775,1776,1779,1780,1783],{},"用 ",[195,1777,1778],{},"jstack"," \u002F ",[195,1781,1782],{},"jcmd Thread.print"," 对应 Java 栈；",[22,1785,1786],{},"连续采样，区分死循环、频繁 GC、锁自旋和热点计算；",[22,1788,1789],{},"必要时使用 JFR 做低侵入分析。",[48,1791,1792],{"id":1792},"内存持续增长",[292,1794,1795,1798,1801,1804,1807,1810],{},[22,1796,1797],{},"区分 Java 堆、Metaspace、Direct Memory、线程栈和 Native Memory；",[22,1799,1800],{},"查看 GC 后存活量是否持续上升；",[22,1802,1803],{},"安全条件下生成 heap dump；",[22,1805,1806],{},"使用 MAT 分析 Dominator Tree 和 GC Roots 保留路径；",[22,1808,1809],{},"对比多个时间点，而不是只看一次快照；",[22,1811,1812],{},"修复引用链，再决定是否调整容量。",[48,1814,1816],{"id":1815},"gc-停顿高","GC 停顿高",[15,1818,1819],{},"关注：",[19,1821,1822,1825,1828,1831,1834,1837,1840],{},[22,1823,1824],{},"分配速率与晋升速率；",[22,1826,1827],{},"GC 后存活集大小；",[22,1829,1830],{},"大对象与 Humongous 分配；",[22,1832,1833],{},"Mixed GC 效果；",[22,1835,1836],{},"safepoint 原因；",[22,1838,1839],{},"容器 CPU 限额和内存限制；",[22,1841,1842],{},"日志、网络或锁是否让应用误以为是 GC 停顿。",[15,1844,1845],{},"先测量，再调参数。盲目增大堆可能降低 GC 频率，却增加单次停顿和故障恢复成本。",[10,1847,1849],{"id":1848},"十二常见误区","十二、常见误区",[292,1851,1852,1855,1861,1864,1867,1872,1875,1878],{},[22,1853,1854],{},"JMM 不是 JVM 堆、栈、方法区划分；",[22,1856,1857,1858,1860],{},"元空间不受 ",[195,1859,1293],{}," 直接控制；",[22,1862,1863],{},"Young GC 不等于整个应用完全无停顿；",[22,1865,1866],{},"Full GC 不一定只由老年代满触发；",[22,1868,1869,1871],{},[195,1870,1482],{}," 是请求，不是语言层面的绝对保证；",[22,1873,1874],{},"对象不可达才是回收前提，不是“方法结束就立即回收”；",[22,1876,1877],{},"堆内存正常不代表进程内存正常；",[22,1879,1880],{},"低停顿收集器仍需付出 CPU、内存或吞吐成本。",[10,1882,1884],{"id":1883},"十三面试回答主线","十三、面试回答主线",[489,1886,1887],{},[15,1888,1889],{},"JVM 内存问题首先要区分逻辑区域和 HotSpot 实现：对象主要在堆，线程调用状态在虚拟机栈，类元数据在现代 HotSpot 的本地内存 Metaspace。GC 从 Roots 做可达性分析，现代收集器根据吞吐、停顿和内存开销做不同取舍。线上排障不能看到 OOM 就调大 Xmx，而应先区分堆、元空间、直接内存和线程，再结合 GC 日志、线程栈、JFR 与 heap dump 定位保留路径和增长原因。",[10,1891,496],{"id":496},[19,1893,1894,1901,1908,1915],{},[22,1895,1896],{},[502,1897,1900],{"href":1898,"rel":1899},"https:\u002F\u002Fdocs.oracle.com\u002Fjavase\u002Fspecs\u002Fjvms\u002Fse25\u002Fhtml\u002Fjvms-2.html",[506],"Java 虚拟机规范：运行时数据区",[22,1902,1903],{},[502,1904,1907],{"href":1905,"rel":1906},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fgctuning\u002Findex.html",[506],"HotSpot GC 调优指南",[22,1909,1910],{},[502,1911,1914],{"href":1912,"rel":1913},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fgctuning\u002Favailable-collectors.html",[506],"HotSpot 可用垃圾收集器",[22,1916,1917],{},[502,1918,1921],{"href":1919,"rel":1920},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fgctuning\u002Fother-considerations.html",[506],"HotSpot 类元数据与 Metaspace",[1923,1924,1925],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":193,"searchDepth":530,"depth":530,"links":1927},[1928,1931,1932,1936,1939,1942,1947,1953,1958,1959,1966,1971,1972,1973],{"id":1211,"depth":530,"text":1212,"children":1929},[1930],{"id":711,"depth":536,"text":711},{"id":1305,"depth":530,"text":1306},{"id":1354,"depth":530,"text":1355,"children":1933},[1934,1935],{"id":1367,"depth":536,"text":1368},{"id":1377,"depth":536,"text":1377},{"id":1397,"depth":530,"text":1398,"children":1937},[1938],{"id":1424,"depth":536,"text":1424},{"id":1441,"depth":530,"text":1442,"children":1940},[1941],{"id":1462,"depth":536,"text":1462},{"id":1489,"depth":530,"text":1490,"children":1943},[1944,1945,1946],{"id":1493,"depth":536,"text":1494},{"id":1500,"depth":536,"text":1501},{"id":1507,"depth":536,"text":1508},{"id":1517,"depth":530,"text":1518,"children":1948},[1949,1950,1951,1952],{"id":1521,"depth":536,"text":1522},{"id":1528,"depth":536,"text":1529},{"id":1558,"depth":536,"text":1559},{"id":1565,"depth":536,"text":1566},{"id":1572,"depth":530,"text":1573,"children":1954},[1955,1956,1957],{"id":1585,"depth":536,"text":1585},{"id":1615,"depth":536,"text":1615},{"id":1635,"depth":536,"text":1635},{"id":1655,"depth":530,"text":1656},{"id":1716,"depth":530,"text":1717,"children":1960},[1961,1962,1963,1964,1965],{"id":1720,"depth":536,"text":1721},{"id":1727,"depth":536,"text":1728},{"id":1734,"depth":536,"text":1735},{"id":1741,"depth":536,"text":1742},{"id":1752,"depth":536,"text":1753},{"id":1759,"depth":530,"text":1760,"children":1967},[1968,1969,1970],{"id":1763,"depth":536,"text":1764},{"id":1792,"depth":536,"text":1792},{"id":1815,"depth":536,"text":1816},{"id":1848,"depth":530,"text":1849},{"id":1883,"depth":530,"text":1884},{"id":496,"depth":530,"text":496},"wiki:java:jvm-core-interview","从运行时数据区、对象分配和垃圾回收到类加载、内存泄漏与线上排障，系统复习 JVM。",{},21,"\u002Fjava\u002Fjvm-core-interview",{"title":769,"description":1975},"java\u002Fjvm-core-interview","z761e1fyRkCnLf5d79U-ZqwGDc16BIPdEPdJjIp6p48",{"id":1983,"title":1984,"body":1985,"commentId":3288,"description":3289,"difficulty":566,"draft":567,"extension":568,"meta":3290,"navigation":570,"order":3291,"path":3292,"section":922,"seo":3293,"stem":3294,"updated":3295,"__hash__":3296},"java\u002Fjava\u002Fjava-memory-model.md","Java 内存模型（JMM）：可见性、有序性与安全发布",{"type":7,"value":1986,"toc":3255},[1987,1994,1997,2002,2006,2009,2048,2051,2055,2058,2144,2159,2162,2164,2167,2176,2186,2189,2208,2210,2213,2216,2238,2240,2246,2250,2253,2260,2263,2344,2347,2404,2418,2422,2427,2435,2438,2444,2450,2465,2471,2475,2556,2565,2569,2574,2592,2595,2650,2654,2666,2713,2719,2723,2730,2776,2779,2797,2801,2816,2825,2828,2831,2834,2916,2919,2922,2959,2966,2984,2990,2994,3000,3074,3077,3092,3095,3098,3118,3127,3130,3133,3136,3142,3144,3148,3151,3155,3161,3165,3171,3175,3178,3182,3185,3188,3193,3196,3235,3237,3253],[15,1988,1989,1990,1993],{},"Java 内存模型（Java Memory Model，JMM）描述的是",[1151,1991,1992],{},"多线程如何正确读写共享变量","。它规定了什么样的执行结果是合法的，以及程序需要通过哪些同步机制建立线程间的可见性与顺序保证。",[15,1995,1996],{},"一句话概括：",[489,1998,1999],{},[15,2000,2001],{},"JMM 解决的不是“数据放在哪里”，而是“一个线程写入的数据，另一个线程何时、以什么顺序能够看到”。",[10,2003,2005],{"id":2004},"jmm-不是-jvm-运行时内存区域","JMM 不是 JVM 运行时内存区域",[15,2007,2008],{},"这两个概念名称相似，但关注的问题完全不同。",[95,2010,2011,2024],{},[98,2012,2013],{},[101,2014,2015,2018,2021],{},[104,2016,2017],{},"概念",[104,2019,2020],{},"关注点",[104,2022,2023],{},"典型内容",[114,2025,2026,2037],{},[101,2027,2028,2031,2034],{},[119,2029,2030],{},"JVM 运行时内存区域",[119,2032,2033],{},"数据存放在哪里",[119,2035,2036],{},"堆、虚拟机栈、方法区、程序计数器",[101,2038,2039,2042,2045],{},[119,2040,2041],{},"Java 内存模型 JMM",[119,2043,2044],{},"多线程如何访问共享数据",[119,2046,2047],{},"原子性、可见性、有序性、happens-before",[15,2049,2050],{},"JMM 中的“主内存”和“工作内存”也是规范层面的抽象。可以借助 CPU 缓存、寄存器和编译器临时值来帮助理解，但不能把它们机械地等同于 Java 堆或某一级 CPU Cache。",[10,2052,2054],{"id":2053},"为什么需要-jmm","为什么需要 JMM",[15,2056,2057],{},"现代 JVM 和处理器会使用缓存、寄存器、指令重排序等手段提高性能。只要单线程结果不变，编译器和 CPU 就可以调整部分操作的执行方式；但在缺少同步的多线程程序中，其他线程可能观察到旧值或不符合源码直觉的顺序。",[187,2059,2061],{"className":1315,"code":2060,"language":1317,"meta":193,"style":193},"class Switch {\n    private boolean ready = false;\n    private int value = 0;\n\n    void write() {\n        value = 42;\n        ready = true;\n    }\n\n    void read() {\n        if (ready) {\n            System.out.println(value);\n        }\n    }\n}\n",[195,2062,2063,2068,2073,2078,2083,2088,2093,2099,2105,2110,2116,2122,2128,2134,2139],{"__ignoreMap":193},[1321,2064,2065],{"class":1323,"line":660},[1321,2066,2067],{},"class Switch {\n",[1321,2069,2070],{"class":1323,"line":530},[1321,2071,2072],{},"    private boolean ready = false;\n",[1321,2074,2075],{"class":1323,"line":536},[1321,2076,2077],{},"    private int value = 0;\n",[1321,2079,2080],{"class":1323,"line":1028},[1321,2081,2082],{"emptyLinePlaceholder":570},"\n",[1321,2084,2085],{"class":1323,"line":1126},[1321,2086,2087],{},"    void write() {\n",[1321,2089,2090],{"class":1323,"line":1199},[1321,2091,2092],{},"        value = 42;\n",[1321,2094,2096],{"class":1323,"line":2095},7,[1321,2097,2098],{},"        ready = true;\n",[1321,2100,2102],{"class":1323,"line":2101},8,[1321,2103,2104],{},"    }\n",[1321,2106,2108],{"class":1323,"line":2107},9,[1321,2109,2082],{"emptyLinePlaceholder":570},[1321,2111,2113],{"class":1323,"line":2112},10,[1321,2114,2115],{},"    void read() {\n",[1321,2117,2119],{"class":1323,"line":2118},11,[1321,2120,2121],{},"        if (ready) {\n",[1321,2123,2125],{"class":1323,"line":2124},12,[1321,2126,2127],{},"            System.out.println(value);\n",[1321,2129,2131],{"class":1323,"line":2130},13,[1321,2132,2133],{},"        }\n",[1321,2135,2137],{"class":1323,"line":2136},14,[1321,2138,2104],{},[1321,2140,2142],{"class":1323,"line":2141},15,[1321,2143,1706],{},[15,2145,2146,2147,2150,2151,2154,2155,2158],{},"如果没有任何同步措施，读线程不一定及时看到 ",[195,2148,2149],{},"ready = true","；即使看到了 ",[195,2152,2153],{},"ready","，也不能仅凭源码顺序推断它一定看到了 ",[195,2156,2157],{},"value = 42","。JMM 通过同步动作和 happens-before 规则为这种跨线程通信建立保证。",[10,2160,2161],{"id":2161},"并发正确性的三个维度",[48,2163,810],{"id":810},[15,2165,2166],{},"一个操作不可分割，要么完整执行，要么不执行。",[187,2168,2170],{"className":1315,"code":2169,"language":1317,"meta":193,"style":193},"count++;\n",[195,2171,2172],{"__ignoreMap":193},[1321,2173,2174],{"class":1323,"line":660},[1321,2175,2169],{},[15,2177,2178,2179,2182,2183,2185],{},"它至少包含读取、加一、写回三个步骤，多个线程并发执行时可能丢失更新。即使把 ",[195,2180,2181],{},"count"," 声明为 ",[195,2184,806],{},"，也不能让这组复合操作具备原子性。",[15,2187,2188],{},"常用保证方式包括：",[19,2190,2191,2199,2205],{},[22,2192,2193,2195,2196],{},[195,2194,843],{}," 或 ",[195,2197,2198],{},"Lock",[22,2200,2201,2202],{},"CAS 和原子类，如 ",[195,2203,2204],{},"AtomicInteger",[22,2206,2207],{},"将共享状态封装到线程安全的数据结构中",[48,2209,800],{"id":800},[15,2211,2212],{},"一个线程修改共享变量后，其他线程能否及时看到修改结果。",[15,2214,2215],{},"常见的可见性保证来自：",[19,2217,2218,2223,2226,2235],{},[22,2219,2220,2222],{},[195,2221,806],{}," 读写",[22,2224,2225],{},"同一把锁的释放和获取",[22,2227,2228,306,2231,2234],{},[195,2229,2230],{},"Thread.start()",[195,2232,2233],{},"Thread.join()"," 等线程生命周期规则",[22,2236,2237],{},"并发容器和线程安全队列提供的内存一致性语义",[48,2239,823],{"id":823},[15,2241,2242,2243,2245],{},"编译器和处理器可以在不改变单线程结果的前提下重排序，这就是 as-if-serial 语义。并发程序需要通过 ",[195,2244,806],{},"、锁等机制约束那些会影响跨线程观察结果的重排序。",[10,2247,2249],{"id":2248},"happens-before判断可见性的核心规则","happens-before：判断可见性的核心规则",[15,2251,2252],{},"如果操作 A happens-before 操作 B，那么 A 的结果必须对 B 可见，并且 JMM 保证 A 在 B 之前有序。",[15,2254,2255,2256,2259],{},"它是一种",[1151,2257,2258],{},"内存可见性和顺序保证","，不应简单理解成现实时间上的“先发生”。",[15,2261,2262],{},"常见规则如下：",[95,2264,2265,2275],{},[98,2266,2267],{},[101,2268,2269,2272],{},[104,2270,2271],{},"规则",[104,2273,2274],{},"含义",[114,2276,2277,2285,2293,2301,2313,2325,2336],{},[101,2278,2279,2282],{},[119,2280,2281],{},"程序次序规则",[119,2283,2284],{},"单线程内，按程序顺序位于前面的操作 happens-before 后面的操作",[101,2286,2287,2290],{},[119,2288,2289],{},"监视器锁规则",[119,2291,2292],{},"对一把锁的解锁 happens-before 后续对同一把锁的加锁",[101,2294,2295,2298],{},[119,2296,2297],{},"volatile 规则",[119,2299,2300],{},"对一个 volatile 变量的写 happens-before 后续对它的读",[101,2302,2303,2306],{},[119,2304,2305],{},"线程启动规则",[119,2307,2308,2309,2312],{},"调用 ",[195,2310,2311],{},"start()"," 前的操作 happens-before 新线程中的操作",[101,2314,2315,2318],{},[119,2316,2317],{},"线程终止规则",[119,2319,2320,2321,2324],{},"线程内的操作 happens-before 其他线程从 ",[195,2322,2323],{},"join()"," 成功返回",[101,2326,2327,2330],{},[119,2328,2329],{},"中断规则",[119,2331,2308,2332,2335],{},[195,2333,2334],{},"interrupt()"," happens-before 目标线程检测到中断",[101,2337,2338,2341],{},[119,2339,2340],{},"传递性",[119,2342,2343],{},"A happens-before B，B happens-before C，则 A happens-before C",[15,2345,2346],{},"例如：",[187,2348,2350],{"className":1315,"code":2349,"language":1317,"meta":193,"style":193},"int data = 0;\nvolatile boolean ready = false;\n\n\u002F\u002F 线程 A\ndata = 42;\nready = true;\n\n\u002F\u002F 线程 B\nif (ready) {\n    System.out.println(data); \u002F\u002F 能看到 42\n}\n",[195,2351,2352,2357,2362,2366,2371,2376,2381,2385,2390,2395,2400],{"__ignoreMap":193},[1321,2353,2354],{"class":1323,"line":660},[1321,2355,2356],{},"int data = 0;\n",[1321,2358,2359],{"class":1323,"line":530},[1321,2360,2361],{},"volatile boolean ready = false;\n",[1321,2363,2364],{"class":1323,"line":536},[1321,2365,2082],{"emptyLinePlaceholder":570},[1321,2367,2368],{"class":1323,"line":1028},[1321,2369,2370],{},"\u002F\u002F 线程 A\n",[1321,2372,2373],{"class":1323,"line":1126},[1321,2374,2375],{},"data = 42;\n",[1321,2377,2378],{"class":1323,"line":1199},[1321,2379,2380],{},"ready = true;\n",[1321,2382,2383],{"class":1323,"line":2095},[1321,2384,2082],{"emptyLinePlaceholder":570},[1321,2386,2387],{"class":1323,"line":2101},[1321,2388,2389],{},"\u002F\u002F 线程 B\n",[1321,2391,2392],{"class":1323,"line":2107},[1321,2393,2394],{},"if (ready) {\n",[1321,2396,2397],{"class":1323,"line":2112},[1321,2398,2399],{},"    System.out.println(data); \u002F\u002F 能看到 42\n",[1321,2401,2402],{"class":1323,"line":2118},[1321,2403,1706],{},[15,2405,2406,2409,2410,2413,2414,2417],{},[195,2407,2408],{},"data = 42"," 位于 volatile 写之前；线程 B 读取到 ",[195,2411,2412],{},"ready == true"," 后，借助程序次序、volatile 规则和传递性，也能看到此前对 ",[195,2415,2416],{},"data"," 的写入。",[10,2419,2421],{"id":2420},"volatile-的能力与边界","volatile 的能力与边界",[15,2423,2424,2426],{},[195,2425,806],{}," 主要提供两类保证：",[292,2428,2429,2432],{},[22,2430,2431],{},"对该变量的写入能够被后续读取它的线程看到。",[22,2433,2434],{},"禁止影响发布与读取语义的特定重排序。",[15,2436,2437],{},"可以将它理解为写端的 release 和读端的 acquire：",[187,2439,2442],{"className":2440,"code":2441,"language":192,"meta":193},[190],"线程 A 的普通写\n       ↓\nvolatile 写（发布）\n       ↓ happens-before\nvolatile 读（获取）\n       ↓\n线程 B 的普通读\n",[195,2443,2441],{"__ignoreMap":193},[15,2445,2446,2447,2449],{},"但 ",[195,2448,806],{}," 不提供复合操作的互斥性：",[187,2451,2453],{"className":1315,"code":2452,"language":1317,"meta":193,"style":193},"volatile int count = 0;\ncount++; \u002F\u002F 仍然不是线程安全的\n",[195,2454,2455,2460],{"__ignoreMap":193},[1321,2456,2457],{"class":1323,"line":660},[1321,2458,2459],{},"volatile int count = 0;\n",[1321,2461,2462],{"class":1323,"line":530},[1321,2463,2464],{},"count++; \u002F\u002F 仍然不是线程安全的\n",[15,2466,2467,2468,2470],{},"适合 ",[195,2469,806],{}," 的典型场景是状态标志、不可变快照的引用和读多写少的整体配置切换。",[48,2472,2474],{"id":2473},"dcl-单例为什么需要-volatile","DCL 单例为什么需要 volatile",[187,2476,2478],{"className":1315,"code":2477,"language":1317,"meta":193,"style":193},"public final class Singleton {\n    private static volatile Singleton instance;\n\n    private Singleton() {}\n\n    public static Singleton getInstance() {\n        if (instance == null) {\n            synchronized (Singleton.class) {\n                if (instance == null) {\n                    instance = new Singleton();\n                }\n            }\n        }\n        return instance;\n    }\n}\n",[195,2479,2480,2485,2490,2494,2499,2503,2508,2513,2518,2523,2528,2533,2538,2542,2547,2551],{"__ignoreMap":193},[1321,2481,2482],{"class":1323,"line":660},[1321,2483,2484],{},"public final class Singleton {\n",[1321,2486,2487],{"class":1323,"line":530},[1321,2488,2489],{},"    private static volatile Singleton instance;\n",[1321,2491,2492],{"class":1323,"line":536},[1321,2493,2082],{"emptyLinePlaceholder":570},[1321,2495,2496],{"class":1323,"line":1028},[1321,2497,2498],{},"    private Singleton() {}\n",[1321,2500,2501],{"class":1323,"line":1126},[1321,2502,2082],{"emptyLinePlaceholder":570},[1321,2504,2505],{"class":1323,"line":1199},[1321,2506,2507],{},"    public static Singleton getInstance() {\n",[1321,2509,2510],{"class":1323,"line":2095},[1321,2511,2512],{},"        if (instance == null) {\n",[1321,2514,2515],{"class":1323,"line":2101},[1321,2516,2517],{},"            synchronized (Singleton.class) {\n",[1321,2519,2520],{"class":1323,"line":2107},[1321,2521,2522],{},"                if (instance == null) {\n",[1321,2524,2525],{"class":1323,"line":2112},[1321,2526,2527],{},"                    instance = new Singleton();\n",[1321,2529,2530],{"class":1323,"line":2118},[1321,2531,2532],{},"                }\n",[1321,2534,2535],{"class":1323,"line":2124},[1321,2536,2537],{},"            }\n",[1321,2539,2540],{"class":1323,"line":2130},[1321,2541,2133],{},[1321,2543,2544],{"class":1323,"line":2136},[1321,2545,2546],{},"        return instance;\n",[1321,2548,2549],{"class":1323,"line":2141},[1321,2550,2104],{},[1321,2552,2554],{"class":1323,"line":2553},16,[1321,2555,1706],{},[15,2557,2558,2559,2561,2562,2564],{},"对象创建可以近似看作分配内存、初始化对象、赋值引用。缺少 ",[195,2560,806],{}," 时，引用赋值可能被其他线程观察到，而对象初始化结果尚未通过正确的同步关系对它可见。",[195,2563,806],{}," 既限制关键重排序，也安全发布了构造完成的引用。",[10,2566,2568],{"id":2567},"synchronized-提供什么保证","synchronized 提供什么保证",[15,2570,2571,2573],{},[195,2572,843],{}," 同时提供：",[19,2575,2576,2581,2586],{},[22,2577,2578,2580],{},[1151,2579,810],{},"：同一时刻只有持有同一把锁的线程进入临界区。",[22,2582,2583,2585],{},[1151,2584,800],{},"：前一个线程解锁前的写入，对后续获得同一把锁的线程可见。",[22,2587,2588,2591],{},[1151,2589,2590],{},"必要的有序性","：锁边界限制会破坏同步语义的重排序。",[15,2593,2594],{},"对比来看：",[95,2596,2597,2608],{},[98,2598,2599],{},[101,2600,2601,2604,2606],{},[104,2602,2603],{},"能力",[104,2605,806],{},[104,2607,843],{},[114,2609,2610,2619,2628,2639],{},[101,2611,2612,2614,2617],{},[119,2613,800],{},[119,2615,2616],{},"保证",[119,2618,2616],{},[101,2620,2621,2624,2626],{},[119,2622,2623],{},"特定有序性",[119,2625,2616],{},[119,2627,2616],{},[101,2629,2630,2633,2636],{},[119,2631,2632],{},"复合操作原子性",[119,2634,2635],{},"不保证",[119,2637,2638],{},"保证临界区内的原子性",[101,2640,2641,2644,2647],{},[119,2642,2643],{},"竞争时是否阻塞",[119,2645,2646],{},"不阻塞",[119,2648,2649],{},"可能阻塞",[10,2651,2653],{"id":2652},"final-字段的内存语义","final 字段的内存语义",[15,2655,2656,2659,2660,2662,2663,2665],{},[195,2657,2658],{},"final"," 不只是“赋值后不能修改”。JMM 还为正确构造对象的 ",[195,2661,2658],{}," 字段提供特殊初始化保证：构造器中对 ",[195,2664,2658],{}," 字段的写入，不能被重排到对象引用发布之后。",[187,2667,2669],{"className":1315,"code":2668,"language":1317,"meta":193,"style":193},"final class Config {\n    private final int timeout;\n    private final String endpoint;\n\n    Config(int timeout, String endpoint) {\n        this.timeout = timeout;\n        this.endpoint = endpoint;\n    }\n}\n",[195,2670,2671,2676,2681,2686,2690,2695,2700,2705,2709],{"__ignoreMap":193},[1321,2672,2673],{"class":1323,"line":660},[1321,2674,2675],{},"final class Config {\n",[1321,2677,2678],{"class":1323,"line":530},[1321,2679,2680],{},"    private final int timeout;\n",[1321,2682,2683],{"class":1323,"line":536},[1321,2684,2685],{},"    private final String endpoint;\n",[1321,2687,2688],{"class":1323,"line":1028},[1321,2689,2082],{"emptyLinePlaceholder":570},[1321,2691,2692],{"class":1323,"line":1126},[1321,2693,2694],{},"    Config(int timeout, String endpoint) {\n",[1321,2696,2697],{"class":1323,"line":1199},[1321,2698,2699],{},"        this.timeout = timeout;\n",[1321,2701,2702],{"class":1323,"line":2095},[1321,2703,2704],{},"        this.endpoint = endpoint;\n",[1321,2706,2707],{"class":1323,"line":2101},[1321,2708,2104],{},[1321,2710,2711],{"class":1323,"line":2107},[1321,2712,1706],{},[15,2714,2715,2716,2718],{},"只要对象在构造过程中没有逸出，其他线程获得该对象后，能看到 ",[195,2717,2658],{}," 字段的正确初始化值。",[48,2720,2722],{"id":2721},"构造期间不要让-this-逸出","构造期间不要让 this 逸出",[15,2724,2725,2726,2729],{},"下面的写法会在对象尚未构造完成时把 ",[195,2727,2728],{},"this"," 暴露出去：",[187,2731,2733],{"className":1315,"code":2732,"language":1317,"meta":193,"style":193},"class Listener {\n    static Listener shared;\n    final int value;\n\n    Listener() {\n        shared = this; \u002F\u002F this 提前逸出\n        value = 42;\n    }\n}\n",[195,2734,2735,2740,2745,2750,2754,2759,2764,2768,2772],{"__ignoreMap":193},[1321,2736,2737],{"class":1323,"line":660},[1321,2738,2739],{},"class Listener {\n",[1321,2741,2742],{"class":1323,"line":530},[1321,2743,2744],{},"    static Listener shared;\n",[1321,2746,2747],{"class":1323,"line":536},[1321,2748,2749],{},"    final int value;\n",[1321,2751,2752],{"class":1323,"line":1028},[1321,2753,2082],{"emptyLinePlaceholder":570},[1321,2755,2756],{"class":1323,"line":1126},[1321,2757,2758],{},"    Listener() {\n",[1321,2760,2761],{"class":1323,"line":1199},[1321,2762,2763],{},"        shared = this; \u002F\u002F this 提前逸出\n",[1321,2765,2766],{"class":1323,"line":2095},[1321,2767,2092],{},[1321,2769,2770],{"class":1323,"line":2101},[1321,2771,2104],{},[1321,2773,2774],{"class":1323,"line":2107},[1321,2775,1706],{},[15,2777,2778],{},"常见的构造期间逸出还包括：",[19,2780,2781,2786,2789,2794],{},[22,2782,2783,2784],{},"在构造器中注册监听器并传入 ",[195,2785,2728],{},[22,2787,2788],{},"在构造器中启动访问当前对象的新线程",[22,2790,635,2791,2793],{},[195,2792,2728],{}," 放入静态集合或共享容器",[22,2795,2796],{},"在构造器中调用可能被子类重写并暴露状态的方法",[48,2798,2800],{"id":2799},"final-引用不等于对象不可变","final 引用不等于对象不可变",[187,2802,2804],{"className":1315,"code":2803,"language":1317,"meta":193,"style":193},"final List\u003CString> names = new ArrayList\u003C>();\nnames.add(\"Alice\"); \u002F\u002F 合法\n",[195,2805,2806,2811],{"__ignoreMap":193},[1321,2807,2808],{"class":1323,"line":660},[1321,2809,2810],{},"final List\u003CString> names = new ArrayList\u003C>();\n",[1321,2812,2813],{"class":1323,"line":530},[1321,2814,2815],{},"names.add(\"Alice\"); \u002F\u002F 合法\n",[15,2817,2818,2820,2821,2824],{},[195,2819,2658],{}," 只保证 ",[195,2822,2823],{},"names"," 不能重新指向另一个 List，并不保证 List 内部状态不可修改，更不保证多个线程并发修改安全。构建不可变对象还需要避免修改入口，并对可变集合进行防御性复制。",[10,2826,2827],{"id":2827},"什么是安全发布",[15,2829,2830],{},"安全发布是指：一个线程创建对象后，通过明确的同步机制把它交给其他线程，使其他线程既能看到对象引用，也能看到对象完整初始化后的状态。",[15,2832,2833],{},"常见方式包括：",[95,2835,2836,2849],{},[98,2837,2838],{},[101,2839,2840,2843,2846],{},[104,2841,2842],{},"方式",[104,2844,2845],{},"建立保证的原因",[104,2847,2848],{},"适用场景",[114,2850,2851,2862,2873,2884,2894,2905],{},[101,2852,2853,2856,2859],{},[119,2854,2855],{},"静态初始化",[119,2857,2858],{},"JVM 保证类初始化串行完成，并对后续使用可见",[119,2860,2861],{},"固定单例、常量配置",[101,2863,2864,2867,2870],{},[119,2865,2866],{},"volatile 引用",[119,2868,2869],{},"volatile 写 happens-before 后续读",[119,2871,2872],{},"配置热更新、不可变快照切换",[101,2874,2875,2878,2881],{},[119,2876,2877],{},"synchronized \u002F Lock",[119,2879,2880],{},"同一把锁的释放 happens-before 后续获取",[119,2882,2883],{},"对象发布并伴随复合读写",[101,2885,2886,2888,2891],{},[119,2887,862],{},[119,2889,2890],{},"容器操作提供相应的内存一致性保证",[119,2892,2893],{},"跨线程共享和查找对象",[101,2895,2896,2899,2902],{},[119,2897,2898],{},"BlockingQueue",[119,2900,2901],{},"入队前的操作对成功出队后的线程可见",[119,2903,2904],{},"生产者—消费者",[101,2906,2907,2910,2913],{},[119,2908,2909],{},"AtomicReference",[119,2911,2912],{},"volatile 语义加 CAS",[119,2914,2915],{},"需要条件更新的对象快照",[48,2917,2918],{"id":2918},"静态字段不等于静态初始化",[15,2920,2921],{},"下面只是运行期间对共享静态字段的一次普通写入，并不天然安全：",[187,2923,2925],{"className":1315,"code":2924,"language":1317,"meta":193,"style":193},"class ConfigHolder {\n    static Config config;\n\n    static void init() {\n        config = new Config();\n    }\n}\n",[195,2926,2927,2932,2937,2941,2946,2951,2955],{"__ignoreMap":193},[1321,2928,2929],{"class":1323,"line":660},[1321,2930,2931],{},"class ConfigHolder {\n",[1321,2933,2934],{"class":1323,"line":530},[1321,2935,2936],{},"    static Config config;\n",[1321,2938,2939],{"class":1323,"line":536},[1321,2940,2082],{"emptyLinePlaceholder":570},[1321,2942,2943],{"class":1323,"line":1028},[1321,2944,2945],{},"    static void init() {\n",[1321,2947,2948],{"class":1323,"line":1126},[1321,2949,2950],{},"        config = new Config();\n",[1321,2952,2953],{"class":1323,"line":1199},[1321,2954,2104],{},[1321,2956,2957],{"class":1323,"line":2095},[1321,2958,1706],{},[15,2960,2961,2962,2965],{},"真正依赖 JVM 类初始化进行安全发布的是字段初始化表达式或 ",[195,2963,2964],{},"static"," 代码块：",[187,2967,2969],{"className":1315,"code":2968,"language":1317,"meta":193,"style":193},"class ConfigHolder {\n    static final Config CONFIG = new Config();\n}\n",[195,2970,2971,2975,2980],{"__ignoreMap":193},[1321,2972,2973],{"class":1323,"line":660},[1321,2974,2931],{},[1321,2976,2977],{"class":1323,"line":530},[1321,2978,2979],{},"    static final Config CONFIG = new Config();\n",[1321,2981,2982],{"class":1323,"line":536},[1321,2983,1706],{},[15,2985,2986,2987,2989],{},"二者的区别不在于“字段是否为 static”，而在于赋值是否发生在 JVM 的类初始化方法 ",[195,2988,1591],{}," 中。",[10,2991,2993],{"id":2992},"volatile-动态发布构造后整体替换","volatile 动态发布：构造后整体替换",[15,2995,2996,2997,1713],{},"对于运行期配置更新，一个实用模式是：",[1151,2998,2999],{},"先完整构造不可变对象，最后一步写入 volatile 引用",[187,3001,3003],{"className":1315,"code":3002,"language":1317,"meta":193,"style":193},"public final class ConfigCenter {\n    private static volatile Config current =\n            new Config(3000, 2);\n\n    public static Config current() {\n        return current;\n    }\n\n    public static void reload(int timeout, int retries) {\n        Config next = new Config(timeout, retries);\n        current = next; \u002F\u002F 最后一步发布\n    }\n\n    public record Config(int timeout, int retries) {}\n}\n",[195,3004,3005,3010,3015,3020,3024,3029,3034,3038,3042,3047,3052,3057,3061,3065,3070],{"__ignoreMap":193},[1321,3006,3007],{"class":1323,"line":660},[1321,3008,3009],{},"public final class ConfigCenter {\n",[1321,3011,3012],{"class":1323,"line":530},[1321,3013,3014],{},"    private static volatile Config current =\n",[1321,3016,3017],{"class":1323,"line":536},[1321,3018,3019],{},"            new Config(3000, 2);\n",[1321,3021,3022],{"class":1323,"line":1028},[1321,3023,2082],{"emptyLinePlaceholder":570},[1321,3025,3026],{"class":1323,"line":1126},[1321,3027,3028],{},"    public static Config current() {\n",[1321,3030,3031],{"class":1323,"line":1199},[1321,3032,3033],{},"        return current;\n",[1321,3035,3036],{"class":1323,"line":2095},[1321,3037,2104],{},[1321,3039,3040],{"class":1323,"line":2101},[1321,3041,2082],{"emptyLinePlaceholder":570},[1321,3043,3044],{"class":1323,"line":2107},[1321,3045,3046],{},"    public static void reload(int timeout, int retries) {\n",[1321,3048,3049],{"class":1323,"line":2112},[1321,3050,3051],{},"        Config next = new Config(timeout, retries);\n",[1321,3053,3054],{"class":1323,"line":2118},[1321,3055,3056],{},"        current = next; \u002F\u002F 最后一步发布\n",[1321,3058,3059],{"class":1323,"line":2124},[1321,3060,2104],{},[1321,3062,3063],{"class":1323,"line":2130},[1321,3064,2082],{"emptyLinePlaceholder":570},[1321,3066,3067],{"class":1323,"line":2136},[1321,3068,3069],{},"    public record Config(int timeout, int retries) {}\n",[1321,3071,3072],{"class":1323,"line":2141},[1321,3073,1706],{},[15,3075,3076],{},"读线程每次先取得一次快照：",[187,3078,3080],{"className":1315,"code":3079,"language":1317,"meta":193,"style":193},"ConfigCenter.Config snapshot = ConfigCenter.current();\nuse(snapshot.timeout(), snapshot.retries());\n",[195,3081,3082,3087],{"__ignoreMap":193},[1321,3083,3084],{"class":1323,"line":660},[1321,3085,3086],{},"ConfigCenter.Config snapshot = ConfigCenter.current();\n",[1321,3088,3089],{"class":1323,"line":530},[1321,3090,3091],{},"use(snapshot.timeout(), snapshot.retries());\n",[15,3093,3094],{},"这样读线程看到的要么是完整的旧配置，要么是完整的新配置，不会看到一个被逐字段修改的中间状态。",[15,3096,3097],{},"错误方式是先发布引用，再继续修改对象：",[187,3099,3101],{"className":1315,"code":3100,"language":1317,"meta":193,"style":193},"config = new Config(); \u002F\u002F 已经发布\nconfig.setTimeout(3000);\nconfig.setEndpoint(\"https:\u002F\u002Fexample.com\");\n",[195,3102,3103,3108,3113],{"__ignoreMap":193},[1321,3104,3105],{"class":1323,"line":660},[1321,3106,3107],{},"config = new Config(); \u002F\u002F 已经发布\n",[1321,3109,3110],{"class":1323,"line":530},[1321,3111,3112],{},"config.setTimeout(3000);\n",[1321,3114,3115],{"class":1323,"line":536},[1321,3116,3117],{},"config.setEndpoint(\"https:\u002F\u002Fexample.com\");\n",[15,3119,3120,3122,3123,3126],{},[195,3121,806],{}," 修饰的是引用，并不会让对象内部后续修改自动具有原子性。多个写线程如果需要避免相互覆盖，仍要使用锁、",[195,3124,3125],{},"AtomicReference.compareAndSet"," 或版本号。",[10,3128,3129],{"id":3129},"安全发布不等于后续线程安全",[15,3131,3132],{},"安全发布只保证对象交给其他线程的那一刻，初始化状态完整可见。如果发布后对象仍然可变，后续并发读写仍需使用锁、volatile 字段、原子类、并发容器，或者改为不可变对象整体替换。",[15,3134,3135],{},"需要区分三件事：",[187,3137,3140],{"className":3138,"code":3139,"language":192,"meta":193},[190],"正确构造：对象内部初始化完整，构造期间 this 不逸出\n安全发布：通过 happens-before 把完整对象交给其他线程\n后续线程安全：发布后的每一次共享状态修改都有并发保护\n",[195,3141,3139],{"__ignoreMap":193},[10,3143,1171],{"id":1171},[48,3145,3147],{"id":3146},"jmm-就是堆栈方法区","“JMM 就是堆、栈、方法区”",[15,3149,3150],{},"错误。那是 JVM 运行时内存区域；JMM 是并发访问共享变量的规则。",[48,3152,3154],{"id":3153},"volatile-能保证线程安全","“volatile 能保证线程安全”",[15,3156,3157,3158,3160],{},"不准确。它保证可见性和特定有序性，不能让 ",[195,3159,816],{}," 等复合操作原子化。",[48,3162,3164],{"id":3163},"final-对象天然不可变线程安全","“final 对象天然不可变、线程安全”",[15,3166,3167,3168,3170],{},"错误。",[195,3169,2658],{}," 引用不能重新赋值，但引用指向的对象仍可能被修改。",[48,3172,3174],{"id":3173},"字段是-static所以写入就是安全发布","“字段是 static，所以写入就是安全发布”",[15,3176,3177],{},"错误。普通静态方法中的赋值仍然是共享变量的普通写；只有类初始化、volatile、锁等机制才能建立明确的发布保证。",[48,3179,3181],{"id":3180},"cpu-有缓存一致性协议所以不需要-jmm","“CPU 有缓存一致性协议，所以不需要 JMM”",[15,3183,3184],{},"错误。缓存一致性不能单独覆盖 Store Buffer、编译器与 CPU 重排序、JVM 优化和不同硬件内存模型差异。JMM 为 Java 程序提供跨平台的统一语义。",[10,3186,3187],{"id":3187},"面试回答模板",[489,3189,3190],{},[15,3191,3192],{},"JMM 是 Java 并发访问共享变量的规范，核心解决原子性、可见性和有序性问题。它通过 happens-before 规则定义一个线程的写入何时必须对另一个线程可见。volatile 写与后续读、同一把锁的释放与获取、线程 start 和 join 都能建立 happens-before。volatile 适合状态标志和不可变对象引用的安全发布，但不能保证复合操作原子性；synchronized 则同时提供临界区原子性、可见性和必要的有序性。final 字段具有特殊初始化语义，但前提是构造期间 this 不逸出。对象安全发布后，如果还会发生并发修改，仍需要额外的线程安全措施。",[10,3194,3195],{"id":3195},"复习检查清单",[292,3197,3198,3201,3208,3211,3214,3217,3223,3226,3229,3232],{},[22,3199,3200],{},"能否解释 JMM 与 JVM 内存区域的区别？",[22,3202,3203,3204,3207],{},"为什么 ",[195,3205,3206],{},"volatile int count; count++"," 仍不安全？",[22,3209,3210],{},"happens-before 提供的是哪两类保证？",[22,3212,3213],{},"volatile 写和读如何安全发布普通字段的写入？",[22,3215,3216],{},"DCL 单例为什么必须使用 volatile？",[22,3218,3219,3220,3222],{},"final 字段语义的前提为什么是 ",[195,3221,2728],{}," 不逸出？",[22,3224,3225],{},"静态字段与 JVM 静态初始化有什么区别？",[22,3227,3228],{},"安全发布为什么不等于对象后续线程安全？",[22,3230,3231],{},"如何用不可变对象加 volatile 引用实现配置热更新？",[22,3233,3234],{},"多个写线程更新快照时为什么可能还需要 CAS 或锁？",[10,3236,496],{"id":496},[19,3238,3239,3246],{},[22,3240,3241],{},[502,3242,3245],{"href":3243,"rel":3244},"https:\u002F\u002Fdocs.oracle.com\u002Fjavase\u002Fspecs\u002Fjls\u002Fse25\u002Fhtml\u002Fjls-17.html",[506],"Java Language Specification：Threads and Locks",[22,3247,3248],{},[502,3249,3252],{"href":3250,"rel":3251},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Fpackage-summary.html#MemoryVisibility",[506],"Java SE API：java.util.concurrent 的内存一致性属性",[1923,3254,1925],{},{"title":193,"searchDepth":530,"depth":530,"links":3256},[3257,3258,3259,3264,3265,3268,3269,3273,3276,3277,3278,3285,3286,3287],{"id":2004,"depth":530,"text":2005},{"id":2053,"depth":530,"text":2054},{"id":2161,"depth":530,"text":2161,"children":3260},[3261,3262,3263],{"id":810,"depth":536,"text":810},{"id":800,"depth":536,"text":800},{"id":823,"depth":536,"text":823},{"id":2248,"depth":530,"text":2249},{"id":2420,"depth":530,"text":2421,"children":3266},[3267],{"id":2473,"depth":536,"text":2474},{"id":2567,"depth":530,"text":2568},{"id":2652,"depth":530,"text":2653,"children":3270},[3271,3272],{"id":2721,"depth":536,"text":2722},{"id":2799,"depth":536,"text":2800},{"id":2827,"depth":530,"text":2827,"children":3274},[3275],{"id":2918,"depth":536,"text":2918},{"id":2992,"depth":530,"text":2993},{"id":3129,"depth":530,"text":3129},{"id":1171,"depth":530,"text":1171,"children":3279},[3280,3281,3282,3283,3284],{"id":3146,"depth":536,"text":3147},{"id":3153,"depth":536,"text":3154},{"id":3163,"depth":536,"text":3164},{"id":3173,"depth":536,"text":3174},{"id":3180,"depth":536,"text":3181},{"id":3187,"depth":530,"text":3187},{"id":3195,"depth":530,"text":3195},{"id":496,"depth":530,"text":496},"wiki:java:java-memory-model","从原子性、可见性、有序性出发，系统理解 happens-before、volatile、final 字段语义和对象安全发布。",{},31,"\u002Fjava\u002Fjava-memory-model",{"title":1984,"description":3289},"java\u002Fjava-memory-model","2026-07-15","XB6ea7s7ydc5hO9YSGkFZyTqRMEbD1zjRPTfDfa856k",{"id":3298,"title":895,"body":3299,"commentId":4005,"description":4006,"difficulty":566,"draft":567,"extension":568,"meta":4007,"navigation":570,"order":4008,"path":4009,"section":922,"seo":4010,"stem":4011,"updated":576,"__hash__":4012},"java\u002Fjava\u002Fcas-atomic-operations.md",{"type":7,"value":3300,"toc":3972},[3301,3305,3308,3314,3317,3323,3326,3333,3337,3344,3388,3391,3405,3408,3412,3414,3417,3420,3441,3444,3447,3450,3456,3480,3484,3487,3501,3504,3507,3510,3531,3536,3539,3542,3545,3548,3573,3577,3580,3586,3589,3592,3595,3599,3602,3608,3611,3615,3618,3622,3625,3629,3632,3648,3651,3654,3668,3671,3674,3691,3694,3697,3714,3720,3723,3728,3732,3741,3747,3750,3754,3757,3763,3770,3776,3779,3782,3785,3789,3795,3798,3809,3812,3823,3827,3911,3915,3947,3949,3970],[10,3302,3304],{"id":3303},"一cas-是什么","一、CAS 是什么",[15,3306,3307],{},"CAS 是 Compare-And-Set，也常称 Compare-And-Swap。它包含三个操作数：",[187,3309,3312],{"className":3310,"code":3311,"language":192,"meta":193},[190],"内存位置 V\n期望值 A\n新值 B\n",[195,3313,3311],{"__ignoreMap":193},[15,3315,3316],{},"原子语义是：",[187,3318,3321],{"className":3319,"code":3320,"language":192,"meta":193},[190],"如果 V == A：把 V 更新为 B，并返回成功\n如果 V != A：不修改 V，并返回失败\n",[195,3322,3320],{"__ignoreMap":193},[15,3324,3325],{},"比较和写入必须作为不可分割的一次原子操作完成，否则多个线程仍可能在判断与写入之间互相覆盖。",[15,3327,3328],{},[3329,3330],"img",{"alt":3331,"src":3332},"CAS 重试流程与 LongAdder 分段计数结构","\u002Fimages\u002Fwiki\u002Fjava\u002Fcas-and-longadder.svg",[10,3334,3336],{"id":3335},"二java-如何使用-cas","二、Java 如何使用 CAS",[15,3338,3339,3340,3343],{},"Java 中常通过原子类或 ",[195,3341,3342],{},"VarHandle"," 使用 CAS：",[187,3345,3347],{"className":1315,"code":3346,"language":1317,"meta":193,"style":193},"AtomicInteger count = new AtomicInteger(0);\n\nint oldValue;\nint newValue;\ndo {\n    oldValue = count.get();\n    newValue = oldValue + 1;\n} while (!count.compareAndSet(oldValue, newValue));\n",[195,3348,3349,3354,3358,3363,3368,3373,3378,3383],{"__ignoreMap":193},[1321,3350,3351],{"class":1323,"line":660},[1321,3352,3353],{},"AtomicInteger count = new AtomicInteger(0);\n",[1321,3355,3356],{"class":1323,"line":530},[1321,3357,2082],{"emptyLinePlaceholder":570},[1321,3359,3360],{"class":1323,"line":536},[1321,3361,3362],{},"int oldValue;\n",[1321,3364,3365],{"class":1323,"line":1028},[1321,3366,3367],{},"int newValue;\n",[1321,3369,3370],{"class":1323,"line":1126},[1321,3371,3372],{},"do {\n",[1321,3374,3375],{"class":1323,"line":1199},[1321,3376,3377],{},"    oldValue = count.get();\n",[1321,3379,3380],{"class":1323,"line":2095},[1321,3381,3382],{},"    newValue = oldValue + 1;\n",[1321,3384,3385],{"class":1323,"line":2101},[1321,3386,3387],{},"} while (!count.compareAndSet(oldValue, newValue));\n",[15,3389,3390],{},"执行过程是：",[292,3392,3393,3396,3399,3402],{},[22,3394,3395],{},"读取当前值；",[22,3397,3398],{},"基于当前值计算新值；",[22,3400,3401],{},"CAS 尝试更新；",[22,3403,3404],{},"失败说明有其他线程抢先修改，重新读取并重试。",[15,3406,3407],{},"JVM 会根据处理器架构把原子操作映射为相应的原子指令或指令序列。例如 x86 常使用带锁语义的原子指令，部分 RISC 架构可能使用 Load-Linked\u002FStore-Conditional 一类机制。不要把 CAS 固定等同于某一条 CPU 指令。",[10,3409,3411],{"id":3410},"三cas-保证了什么","三、CAS 保证了什么",[48,3413,810],{"id":810},[15,3415,3416],{},"CAS 保证对一个目标变量的“比较并更新”是原子的。",[48,3418,3419],{"id":3419},"可见性和有序性",[15,3421,3422,3423,3426,3427,3430,3431,306,3434,306,3437,3440],{},"Java API 的内存语义由具体方法决定。",[195,3424,3425],{},"AtomicInteger.compareAndSet()"," 具有 ",[195,3428,3429],{},"VarHandle.compareAndSet()"," 对应的 volatile 读写语义；",[195,3432,3433],{},"getAcquire()",[195,3435,3436],{},"setRelease()",[195,3438,3439],{},"compareAndExchangeAcquire()"," 等方法则提供不同强度的内存顺序。",[15,3442,3443],{},"因此不能笼统说“任何 CAS 都自动等同于完整 volatile 屏障”，应看调用的具体 API。",[48,3445,3446],{"id":3446},"不能自动保证复合业务原子性",[15,3448,3449],{},"CAS 只保护参与比较的那个状态。如果业务约束同时依赖多个可独立变化的字段，单字段 CAS 仍可能失败。",[15,3451,3452,3453,3455],{},"可以把多个字段封装进不可变对象，再通过 ",[195,3454,2909],{}," 整体替换：",[187,3457,3459],{"className":1315,"code":3458,"language":1317,"meta":193,"style":193},"record AccountState(long available, long frozen, long version) {}\n\nAtomicReference\u003CAccountState> ref =\n        new AtomicReference\u003C>(new AccountState(100, 0, 1));\n",[195,3460,3461,3466,3470,3475],{"__ignoreMap":193},[1321,3462,3463],{"class":1323,"line":660},[1321,3464,3465],{},"record AccountState(long available, long frozen, long version) {}\n",[1321,3467,3468],{"class":1323,"line":530},[1321,3469,2082],{"emptyLinePlaceholder":570},[1321,3471,3472],{"class":1323,"line":536},[1321,3473,3474],{},"AtomicReference\u003CAccountState> ref =\n",[1321,3476,3477],{"class":1323,"line":1028},[1321,3478,3479],{},"        new AtomicReference\u003C>(new AccountState(100, 0, 1));\n",[10,3481,3483],{"id":3482},"四cas-的优点与局限","四、CAS 的优点与局限",[48,3485,3486],{"id":3486},"优点",[19,3488,3489,3492,3495,3498],{},[22,3490,3491],{},"无竞争或低竞争时路径短；",[22,3493,3494],{},"不需要线程进入阻塞队列；",[22,3496,3497],{},"避免部分挂起、唤醒和上下文切换成本；",[22,3499,3500],{},"适合构建原子类、无锁队列和同步器。",[48,3502,3503],{"id":3503},"高竞争下自旋成本高",[15,3505,3506],{},"大量线程更新同一个变量时，同一时刻只有一个线程成功，其余线程不断重读和重试，会消耗 CPU，并造成缓存行在多个核心之间频繁失效。",[15,3508,3509],{},"常见治理方式：",[19,3511,3512,3515,3518,3525,3528],{},[22,3513,3514],{},"分散热点，例如 LongAdder；",[22,3516,3517],{},"随机退避或限制重试次数；",[22,3519,3520,3521,3524],{},"极短等待中使用 ",[195,3522,3523],{},"Thread.onSpinWait()"," 提示处理器；",[22,3526,3527],{},"竞争持续时间不可控时转为阻塞锁；",[22,3529,3530],{},"重新进行数据分片或串行化。",[15,3532,3533,3535],{},[195,3534,3523],{}," 只优化自旋提示，不提供可见性、原子性或锁语义。",[48,3537,3538],{"id":3538},"不保证公平",[15,3540,3541],{},"CAS 只决定本次竞争谁成功，不维护先来先得。某个线程可能连续失败，理论上存在饥饿风险。",[48,3543,3544],{"id":3544},"只适合可重试操作",[15,3546,3547],{},"CAS 循环中的计算函数可能被重复执行，不能包含不可重复副作用：",[187,3549,3551],{"className":1315,"code":3550,"language":1317,"meta":193,"style":193},"atomic.updateAndGet(old -> {\n    \u002F\u002F 不应在这里扣款、发消息或调用不可重复的远程接口\n    return old + 1;\n});\n",[195,3552,3553,3558,3563,3568],{"__ignoreMap":193},[1321,3554,3555],{"class":1323,"line":660},[1321,3556,3557],{},"atomic.updateAndGet(old -> {\n",[1321,3559,3560],{"class":1323,"line":530},[1321,3561,3562],{},"    \u002F\u002F 不应在这里扣款、发消息或调用不可重复的远程接口\n",[1321,3564,3565],{"class":1323,"line":536},[1321,3566,3567],{},"    return old + 1;\n",[1321,3569,3570],{"class":1323,"line":1028},[1321,3571,3572],{},"});\n",[10,3574,3576],{"id":3575},"五aba-问题","五、ABA 问题",[15,3578,3579],{},"ABA 指一个值经历：",[187,3581,3584],{"className":3582,"code":3583,"language":192,"meta":193},[190],"A → B → A\n",[195,3585,3583],{"__ignoreMap":193},[15,3587,3588],{},"线程 1 读取 A 后暂停；线程 2 把值改为 B，又改回 A；线程 1 恢复时 CAS 仍能成功，因为它只看到当前值还是 A，却不知道状态曾经变化。",[15,3590,3591],{},"ABA 是否有问题取决于业务语义。简单计数可能不关心历史变化，但链表节点复用、资源状态和版本控制可能非常敏感。",[48,3593,3594],{"id":3594},"解决方案",[3596,3597,3598],"h4",{"id":3598},"版本号",[15,3600,3601],{},"把值与版本作为整体比较：",[187,3603,3606],{"className":3604,"code":3605,"language":192,"meta":193},[190],"(A, version=1)\n→ (B, version=2)\n→ (A, version=3)\n",[195,3607,3605],{"__ignoreMap":193},[15,3609,3610],{},"虽然值回到 A，版本已经不同。",[3596,3612,3614],{"id":3613},"atomicstampedreference","AtomicStampedReference",[15,3616,3617],{},"同时维护引用和整数 stamp，CAS 时一起比较。",[3596,3619,3621],{"id":3620},"atomicmarkablereference","AtomicMarkableReference",[15,3623,3624],{},"同时维护引用和布尔标记，适合只关心“是否被删除\u002F修改过”之类的二态信息。",[10,3626,3628],{"id":3627},"六java-原子类体系","六、Java 原子类体系",[48,3630,3631],{"id":3631},"基本类型",[19,3633,3634,3638,3643],{},[22,3635,3636],{},[195,3637,2204],{},[22,3639,3640],{},[195,3641,3642],{},"AtomicLong",[22,3644,3645],{},[195,3646,3647],{},"AtomicBoolean",[15,3649,3650],{},"适合序列号、单状态标记和低到中等竞争计数。",[48,3652,3653],{"id":3653},"引用类型",[19,3655,3656,3660,3664],{},[22,3657,3658],{},[195,3659,2909],{},[22,3661,3662],{},[195,3663,3614],{},[22,3665,3666],{},[195,3667,3621],{},[15,3669,3670],{},"适合不可变状态整体替换、版本控制和 ABA 治理。",[48,3672,3673],{"id":3673},"数组类型",[19,3675,3676,3681,3686],{},[22,3677,3678],{},[195,3679,3680],{},"AtomicIntegerArray",[22,3682,3683],{},[195,3684,3685],{},"AtomicLongArray",[22,3687,3688],{},[195,3689,3690],{},"AtomicReferenceArray",[15,3692,3693],{},"它们保证数组元素的原子访问，不代表多个元素组合操作自动原子。",[48,3695,3696],{"id":3696},"字段更新器",[19,3698,3699,3704,3709],{},[22,3700,3701],{},[195,3702,3703],{},"AtomicIntegerFieldUpdater",[22,3705,3706],{},[195,3707,3708],{},"AtomicLongFieldUpdater",[22,3710,3711],{},[195,3712,3713],{},"AtomicReferenceFieldUpdater",[15,3715,3716,3717,3719],{},"在不为每个对象额外创建 Atomic 包装对象的情况下，对指定 ",[195,3718,806],{}," 字段做原子更新。它们带有反射式访问约束和类型限制。",[48,3721,3342],{"id":3722},"varhandle",[15,3724,3725,3727],{},[195,3726,3342],{}," 可以操作字段、数组元素和某些堆外结构，提供 plain、opaque、acquire\u002Frelease、volatile 和原子更新等多种访问模式。它是现代 JDK 中表达底层内存访问语义的重要 API。",[10,3729,3731],{"id":3730},"七atomiclong-为什么在高竞争下变慢","七、AtomicLong 为什么在高竞争下变慢",[15,3733,3734,3736,3737,3740],{},[195,3735,3642],{}," 只有一个热点 ",[195,3738,3739],{},"value","：",[187,3742,3745],{"className":3743,"code":3744,"language":192,"meta":193},[190],"线程 A ─┐\n线程 B ─┼→ AtomicLong.value\n线程 C ─┤\n线程 D ─┘\n",[195,3746,3744],{"__ignoreMap":193},[15,3748,3749],{},"并发越高，CAS 失败、缓存一致性通信和自旋越多。低竞争时它非常直接，高竞争写入时则可能成为热点。",[10,3751,3753],{"id":3752},"八longadder-如何分散竞争","八、LongAdder 如何分散竞争",[15,3755,3756],{},"LongAdder 的核心思想是：",[187,3758,3761],{"className":3759,"code":3760,"language":192,"meta":193},[190],"base + Cell[]\n",[195,3762,3760],{"__ignoreMap":193},[15,3764,3765,3766,3769],{},"低竞争时优先 CAS 更新 ",[195,3767,3768],{},"base","；出现竞争后，把不同线程分散到不同 Cell。最终：",[187,3771,3774],{"className":3772,"code":3773,"language":192,"meta":193},[190],"sum = base + Σ Cell.value\n",[195,3775,3773],{"__ignoreMap":193},[15,3777,3778],{},"如果某个 Cell 仍然冲突，线程会重新计算探针并尝试其他槽位；必要时扩容 Cell 数组。",[48,3780,3781],{"id":3781},"空间换时间",[15,3783,3784],{},"LongAdder 使用更多内存换取更低热点竞争。Cell 通常还会采取缓存行填充等手段，降低不同 Cell 之间的伪共享。",[48,3786,3788],{"id":3787},"sum-不是严格原子快照","sum 不是严格原子快照",[15,3790,3791,3794],{},[195,3792,3793],{},"sum()"," 汇总期间，其他线程仍可更新不同 Cell，因此结果不代表一个全局锁保护下的绝对瞬时值。",[15,3796,3797],{},"LongAdder 适合：",[19,3799,3800,3803,3806],{},[22,3801,3802],{},"QPS、调用次数、命中次数；",[22,3804,3805],{},"监控指标；",[22,3807,3808],{},"高并发统计。",[15,3810,3811],{},"不适合：",[19,3813,3814,3817,3820],{},[22,3815,3816],{},"唯一序列号；",[22,3818,3819],{},"账户余额、精确库存；",[22,3821,3822],{},"必须基于当前精确值继续决策的业务。",[10,3824,3826],{"id":3825},"九atomiclong-与-longadder-如何选","九、AtomicLong 与 LongAdder 如何选",[95,3828,3829,3841],{},[98,3830,3831],{},[101,3832,3833,3836,3838],{},[104,3834,3835],{},"维度",[104,3837,3642],{},[104,3839,3840],{},"LongAdder",[114,3842,3843,3856,3867,3878,3889,3900],{},[101,3844,3845,3848,3851],{},[119,3846,3847],{},"数据结构",[119,3849,3850],{},"单个 value",[119,3852,3853,3854],{},"base + Cell",[1321,3855],{},[101,3857,3858,3861,3864],{},[119,3859,3860],{},"低竞争",[119,3862,3863],{},"简单直接",[119,3865,3866],{},"略有额外逻辑",[101,3868,3869,3872,3875],{},[119,3870,3871],{},"高并发写",[119,3873,3874],{},"热点明显",[119,3876,3877],{},"分散竞争，吞吐更好",[101,3879,3880,3883,3886],{},[119,3881,3882],{},"读取",[119,3884,3885],{},"单值读取",[119,3887,3888],{},"汇总多个槽位",[101,3890,3891,3894,3897],{},[119,3892,3893],{},"一致性",[119,3895,3896],{},"单变量原子读写",[119,3898,3899],{},"sum 非严格原子快照",[101,3901,3902,3905,3908],{},[119,3903,3904],{},"典型用途",[119,3906,3907],{},"序列、状态、精确原子值",[119,3909,3910],{},"指标和高并发统计",[10,3912,3914],{"id":3913},"十高频面试问题","十、高频面试问题",[292,3916,3917,3920,3923,3926,3929,3932,3935,3938,3941,3944],{},[22,3918,3919],{},"CAS 的三个操作数是什么？",[22,3921,3922],{},"CAS 为什么具有原子性？",[22,3924,3925],{},"CAS 与 volatile 分别解决什么问题？",[22,3927,3928],{},"高竞争下 CAS 为什么变慢？",[22,3930,3931],{},"什么是 ABA，什么时候有实际危害？",[22,3933,3934],{},"AtomicStampedReference 如何解决 ABA？",[22,3936,3937],{},"AtomicLong 和 LongAdder 有什么区别？",[22,3939,3940],{},"LongAdder 为什么不能用作严格序列号？",[22,3942,3943],{},"CAS 循环中的函数为什么应避免副作用？",[22,3945,3946],{},"VarHandle 的 acquire\u002Frelease 与 volatile 模式有什么差别？",[10,3948,496],{"id":496},[19,3950,3951,3958,3964],{},[22,3952,3953],{},[502,3954,3957],{"href":3955,"rel":3956},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Fatomic\u002Fpackage-summary.html",[506],"Java 原子类包",[22,3959,3960],{},[502,3961,3642],{"href":3962,"rel":3963},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Fatomic\u002FAtomicLong.html",[506],[22,3965,3966],{},[502,3967,3342],{"href":3968,"rel":3969},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Flang\u002Finvoke\u002FVarHandle.html",[506],[1923,3971,1925],{},{"title":193,"searchDepth":530,"depth":530,"links":3973},[3974,3975,3976,3981,3987,3990,3997,3998,4002,4003,4004],{"id":3303,"depth":530,"text":3304},{"id":3335,"depth":530,"text":3336},{"id":3410,"depth":530,"text":3411,"children":3977},[3978,3979,3980],{"id":810,"depth":536,"text":810},{"id":3419,"depth":536,"text":3419},{"id":3446,"depth":536,"text":3446},{"id":3482,"depth":530,"text":3483,"children":3982},[3983,3984,3985,3986],{"id":3486,"depth":536,"text":3486},{"id":3503,"depth":536,"text":3503},{"id":3538,"depth":536,"text":3538},{"id":3544,"depth":536,"text":3544},{"id":3575,"depth":530,"text":3576,"children":3988},[3989],{"id":3594,"depth":536,"text":3594},{"id":3627,"depth":530,"text":3628,"children":3991},[3992,3993,3994,3995,3996],{"id":3631,"depth":536,"text":3631},{"id":3653,"depth":536,"text":3653},{"id":3673,"depth":536,"text":3673},{"id":3696,"depth":536,"text":3696},{"id":3722,"depth":536,"text":3342},{"id":3730,"depth":530,"text":3731},{"id":3752,"depth":530,"text":3753,"children":3999},[4000,4001],{"id":3781,"depth":536,"text":3781},{"id":3787,"depth":536,"text":3788},{"id":3825,"depth":530,"text":3826},{"id":3913,"depth":530,"text":3914},{"id":496,"depth":530,"text":496},"wiki:java:cas-atomic-operations","从 Compare-And-Set 的硬件语义出发，理解自旋更新、ABA、Java 原子类和 LongAdder 分段计数。",{},32,"\u002Fjava\u002Fcas-atomic-operations",{"title":895,"description":4006},"java\u002Fcas-atomic-operations","viun3XN2z6zVWEHRE1dpdQZcVsxjZTIJ5A7wYSPqwMI",{"id":4014,"title":901,"body":4015,"commentId":4836,"description":4837,"difficulty":566,"draft":567,"extension":568,"meta":4838,"navigation":570,"order":4839,"path":4840,"section":922,"seo":4841,"stem":4842,"updated":576,"__hash__":4843},"java\u002Fjava\u002Fjava-lock-mechanisms.md",{"type":7,"value":4016,"toc":4796},[4017,4021,4024,4030,4045,4049,4095,4098,4113,4127,4131,4134,4140,4143,4167,4171,4177,4181,4184,4188,4191,4197,4200,4203,4206,4209,4212,4215,4218,4221,4225,4230,4233,4265,4272,4275,4310,4313,4316,4319,4323,4326,4341,4354,4361,4372,4376,4379,4385,4388,4392,4399,4405,4408,4411,4414,4420,4423,4426,4430,4433,4444,4447,4514,4517,4520,4537,4540,4544,4636,4639,4643,4647,4661,4665,4683,4687,4695,4698,4701,4705,4708,4711,4714,4717,4720,4723,4726,4729,4733,4765,4767,4794],[10,4018,4020],{"id":4019},"一锁分类不是互斥标签","一、锁分类不是互斥标签",[15,4022,4023],{},"悲观\u002F乐观、独占\u002F共享、公平\u002F非公平、可重入\u002F不可重入、自旋\u002F阻塞描述的是不同维度。一把锁可以同时具备多个特征。",[15,4025,4026],{},[3329,4027],{"alt":4028,"src":4029},"Java 锁机制分类与典型实现","\u002Fimages\u002Fwiki\u002Fjava\u002Fjava-lock-taxonomy.svg",[15,4031,4032,4033,4036,4037,4040,4041,4044],{},"例如 ",[195,4034,4035],{},"ReentrantLock"," 是悲观、独占、可重入锁，支持公平或非公平策略；",[195,4038,4039],{},"ReentrantReadWriteLock.ReadLock"," 属于共享读锁；",[195,4042,4043],{},"StampedLock"," 支持写锁、悲观读和乐观读，但不可重入。",[10,4046,4048],{"id":4047},"二synchronized-的使用方式","二、synchronized 的使用方式",[187,4050,4052],{"className":1315,"code":4051,"language":1317,"meta":193,"style":193},"public synchronized void instanceMethod() {}\n\npublic static synchronized void staticMethod() {}\n\npublic void block() {\n    synchronized (lock) {\n        \u002F\u002F 临界区\n    }\n}\n",[195,4053,4054,4059,4063,4068,4072,4077,4082,4087,4091],{"__ignoreMap":193},[1321,4055,4056],{"class":1323,"line":660},[1321,4057,4058],{},"public synchronized void instanceMethod() {}\n",[1321,4060,4061],{"class":1323,"line":530},[1321,4062,2082],{"emptyLinePlaceholder":570},[1321,4064,4065],{"class":1323,"line":536},[1321,4066,4067],{},"public static synchronized void staticMethod() {}\n",[1321,4069,4070],{"class":1323,"line":1028},[1321,4071,2082],{"emptyLinePlaceholder":570},[1321,4073,4074],{"class":1323,"line":1126},[1321,4075,4076],{},"public void block() {\n",[1321,4078,4079],{"class":1323,"line":1199},[1321,4080,4081],{},"    synchronized (lock) {\n",[1321,4083,4084],{"class":1323,"line":2095},[1321,4085,4086],{},"        \u002F\u002F 临界区\n",[1321,4088,4089],{"class":1323,"line":2101},[1321,4090,2104],{},[1321,4092,4093],{"class":1323,"line":2107},[1321,4094,1706],{},[15,4096,4097],{},"对应锁对象：",[19,4099,4100,4103,4110],{},[22,4101,4102],{},"实例同步方法锁当前实例；",[22,4104,4105,4106,4109],{},"静态同步方法锁对应 ",[195,4107,4108],{},"Class"," 对象；",[22,4111,4112],{},"同步代码块锁括号中的对象。",[15,4114,4115,4116,639,4119,4122,4123,4126],{},"同步代码块在字节码层面通常使用 ",[195,4117,4118],{},"monitorenter",[195,4120,4121],{},"monitorexit","；同步方法使用 ",[195,4124,4125],{},"ACC_SYNCHRONIZED"," 标志，由方法调用机制进入和退出 Monitor。",[10,4128,4130],{"id":4129},"三objectmonitor-的主要结构","三、ObjectMonitor 的主要结构",[15,4132,4133],{},"每个 Java 对象都可关联 Monitor。竞争升级到需要阻塞管理时，可以用下图理解等待线程的组织方式：",[15,4135,4136],{},[3329,4137],{"alt":4138,"src":4139},"synchronized 对应的 ObjectMonitor 结构","\u002Fimages\u002Fwiki\u002Fjava\u002Fsynchronized-monitor.svg",[15,4141,4142],{},"核心概念：",[19,4144,4145,4148,4151,4158],{},[22,4146,4147],{},"Owner：当前持有 Monitor 的线程；",[22,4149,4150],{},"EntryList：等待进入临界区的竞争线程；",[22,4152,4153,4154,4157],{},"WaitSet：调用 ",[195,4155,4156],{},"wait()"," 后释放 Monitor 并等待条件的线程；",[22,4159,4160,1779,4163,4166],{},[195,4161,4162],{},"notify()",[195,4164,4165],{},"notifyAll()","：使 WaitSet 中线程有资格重新参与锁竞争，不会让其立即执行。",[48,4168,4170],{"id":4169},"wait-为什么必须在-synchronized-中使用","wait 为什么必须在 synchronized 中使用",[15,4172,4173,4174,1713],{},"调用线程必须先拥有对象 Monitor，才能原子地释放锁并进入该 Monitor 的等待集合。否则会抛出 ",[195,4175,4176],{},"IllegalMonitorStateException",[48,4178,4180],{"id":4179},"synchronized-的内存语义","synchronized 的内存语义",[15,4182,4183],{},"对同一 Monitor 的解锁 happens-before 后续加锁。锁既提供临界区互斥，也建立共享数据的可见性和必要有序性。",[10,4185,4187],{"id":4186},"四synchronized-的历史锁优化","四、synchronized 的历史锁优化",[15,4189,4190],{},"经典资料常描述：",[187,4192,4195],{"className":4193,"code":4194,"language":192,"meta":193},[190],"无锁 → 偏向锁 → 轻量级锁 → 重量级锁\n",[195,4196,4194],{"__ignoreMap":193},[15,4198,4199],{},"这反映了 HotSpot 历史实现中的优化思路：无竞争时尽量减少同步成本，短竞争时使用 CAS 和自旋，竞争加剧后使用 Monitor 阻塞管理。",[15,4201,4202],{},"需要注意版本差异：偏向锁在较新 JDK 中已被禁用并移除，不应把它当成所有现代 JDK 的当前执行流程。面试时应把“规范语义”与“某个 HotSpot 版本的优化实现”分开。",[48,4204,4205],{"id":4205},"自旋",[15,4207,4208],{},"锁预计很快释放时，线程可以短暂自旋，避免挂起和唤醒；竞争时间过长则会浪费 CPU。现代 JVM 会根据运行情况决定部分自旋策略，业务代码不要自行模拟复杂的 JVM 锁升级逻辑。",[48,4210,4211],{"id":4211},"锁消除",[15,4213,4214],{},"JIT 通过逃逸分析确认对象不会被其他线程访问时，可以消除不必要的同步。",[48,4216,4217],{"id":4217},"锁粗化",[15,4219,4220],{},"连续对同一对象频繁加锁解锁时，JIT 可能扩大锁范围，减少重复同步成本。",[10,4222,4224],{"id":4223},"五reentrantlock","五、ReentrantLock",[15,4226,4227,4229],{},[195,4228,4035],{}," 基于 AQS 构建，提供与 synchronized 类似的可重入互斥语义，并增加显式控制能力。",[15,4231,4232],{},"标准写法：",[187,4234,4236],{"className":1315,"code":4235,"language":1317,"meta":193,"style":193},"lock.lock();\ntry {\n    \u002F\u002F 临界区\n} finally {\n    lock.unlock();\n}\n",[195,4237,4238,4243,4247,4252,4256,4261],{"__ignoreMap":193},[1321,4239,4240],{"class":1323,"line":660},[1321,4241,4242],{},"lock.lock();\n",[1321,4244,4245],{"class":1323,"line":530},[1321,4246,1681],{},[1321,4248,4249],{"class":1323,"line":536},[1321,4250,4251],{},"    \u002F\u002F 临界区\n",[1321,4253,4254],{"class":1323,"line":1028},[1321,4255,1696],{},[1321,4257,4258],{"class":1323,"line":1126},[1321,4259,4260],{},"    lock.unlock();\n",[1321,4262,4263],{"class":1323,"line":1199},[1321,4264,1706],{},[15,4266,4267,4268,4271],{},"必须在 ",[195,4269,4270],{},"finally"," 中释放，避免异常导致锁永久不释放。",[48,4273,4274],{"id":4274},"主要能力",[19,4276,4277,4283,4289,4295,4301,4304],{},[22,4278,4279,4282],{},[195,4280,4281],{},"lock()","：普通阻塞获取；",[22,4284,4285,4288],{},[195,4286,4287],{},"lockInterruptibly()","：等待时可以响应中断；",[22,4290,4291,4294],{},[195,4292,4293],{},"tryLock()","：立即尝试；",[22,4296,4297,4300],{},[195,4298,4299],{},"tryLock(timeout)","：超时获取；",[22,4302,4303],{},"公平或非公平策略；",[22,4305,4306,4307,1713],{},"一把锁可创建多个 ",[195,4308,4309],{},"Condition",[48,4311,4312],{"id":4312},"公平与非公平",[15,4314,4315],{},"公平锁会优先考虑等待队列中更早的线程，减少插队和饥饿；非公平锁允许新线程先尝试抢锁，通常吞吐更高，因为刚运行的线程可能直接获得锁，减少调度切换。",[15,4317,4318],{},"公平不等于严格实时顺序，也不等于性能更高。",[10,4320,4322],{"id":4321},"六condition","六、Condition",[15,4324,4325],{},"Condition 把一个锁拆出多个条件等待队列：",[187,4327,4329],{"className":1315,"code":4328,"language":1317,"meta":193,"style":193},"Condition notEmpty = lock.newCondition();\nCondition notFull = lock.newCondition();\n",[195,4330,4331,4336],{"__ignoreMap":193},[1321,4332,4333],{"class":1323,"line":660},[1321,4334,4335],{},"Condition notEmpty = lock.newCondition();\n",[1321,4337,4338],{"class":1323,"line":530},[1321,4339,4340],{},"Condition notFull = lock.newCondition();\n",[15,4342,4343,4346,4347,4350,4351,4353],{},[195,4344,4345],{},"await()"," 会把当前线程加入条件队列并完全释放锁；",[195,4348,4349],{},"signal()"," 只是把等待节点转移到 AQS 同步队列，线程仍需重新获取锁后才能从 ",[195,4352,4345],{}," 返回。",[15,4355,4356,4357,4360],{},"与 ",[195,4358,4359],{},"wait\u002Fnotify"," 相比：",[19,4362,4363,4366,4369],{},[22,4364,4365],{},"Object 通常只有一个 WaitSet；",[22,4367,4368],{},"ReentrantLock 可以创建多个 Condition；",[22,4370,4371],{},"Condition 能更精确地唤醒等待某类条件的线程。",[10,4373,4375],{"id":4374},"七reentrantreadwritelock","七、ReentrantReadWriteLock",[15,4377,4378],{},"读写锁的基本规则：",[187,4380,4383],{"className":4381,"code":4382,"language":192,"meta":193},[190],"读锁 + 读锁：可以并发\n读锁 + 写锁：互斥\n写锁 + 写锁：互斥\n",[195,4384,4382],{"__ignoreMap":193},[15,4386,4387],{},"适合读多写少，并且读临界区足够长，值得承担锁管理成本的场景。读操作极短时，普通互斥锁或不可变快照可能更简单。",[48,4389,4391],{"id":4390},"state-如何表示读写状态","state 如何表示读写状态",[15,4393,4394,4395,4398],{},"经典实现中，AQS 的一个 ",[195,4396,4397],{},"int state"," 被拆成两部分：",[187,4400,4403],{"className":4401,"code":4402,"language":192,"meta":193},[190],"高 16 位：共享读锁计数\n低 16 位：独占写锁重入次数\n",[195,4404,4402],{"__ignoreMap":193},[15,4406,4407],{},"每个线程自己的读锁重入次数还需要额外记录，不能只靠全局读计数判断。",[48,4409,4410],{"id":4410},"锁降级",[15,4412,4413],{},"允许写线程在持有写锁时先获取读锁，再释放写锁：",[187,4415,4418],{"className":4416,"code":4417,"language":192,"meta":193},[190],"写锁 → 同时持有读锁 → 释放写锁 → 保留读锁\n",[195,4419,4417],{"__ignoreMap":193},[15,4421,4422],{},"这样可以在完成写入后继续安全读取更新结果。",[15,4424,4425],{},"不应直接从读锁升级为写锁。多个读线程同时升级会彼此等待，容易死锁。通常应释放读锁、获取写锁，并重新检查条件。",[10,4427,4429],{"id":4428},"八stampedlock","八、StampedLock",[15,4431,4432],{},"StampedLock 提供：",[19,4434,4435,4438,4441],{},[22,4436,4437],{},"独占写锁；",[22,4439,4440],{},"悲观读锁；",[22,4442,4443],{},"乐观读 stamp。",[15,4445,4446],{},"乐观读模式：",[187,4448,4450],{"className":1315,"code":4449,"language":1317,"meta":193,"style":193},"long stamp = lock.tryOptimisticRead();\ndouble currentX = x;\ndouble currentY = y;\n\nif (!lock.validate(stamp)) {\n    stamp = lock.readLock();\n    try {\n        currentX = x;\n        currentY = y;\n    } finally {\n        lock.unlockRead(stamp);\n    }\n}\n",[195,4451,4452,4457,4462,4467,4471,4476,4481,4486,4491,4496,4501,4506,4510],{"__ignoreMap":193},[1321,4453,4454],{"class":1323,"line":660},[1321,4455,4456],{},"long stamp = lock.tryOptimisticRead();\n",[1321,4458,4459],{"class":1323,"line":530},[1321,4460,4461],{},"double currentX = x;\n",[1321,4463,4464],{"class":1323,"line":536},[1321,4465,4466],{},"double currentY = y;\n",[1321,4468,4469],{"class":1323,"line":1028},[1321,4470,2082],{"emptyLinePlaceholder":570},[1321,4472,4473],{"class":1323,"line":1126},[1321,4474,4475],{},"if (!lock.validate(stamp)) {\n",[1321,4477,4478],{"class":1323,"line":1199},[1321,4479,4480],{},"    stamp = lock.readLock();\n",[1321,4482,4483],{"class":1323,"line":2095},[1321,4484,4485],{},"    try {\n",[1321,4487,4488],{"class":1323,"line":2101},[1321,4489,4490],{},"        currentX = x;\n",[1321,4492,4493],{"class":1323,"line":2107},[1321,4494,4495],{},"        currentY = y;\n",[1321,4497,4498],{"class":1323,"line":2112},[1321,4499,4500],{},"    } finally {\n",[1321,4502,4503],{"class":1323,"line":2118},[1321,4504,4505],{},"        lock.unlockRead(stamp);\n",[1321,4507,4508],{"class":1323,"line":2124},[1321,4509,2104],{},[1321,4511,4512],{"class":1323,"line":2130},[1321,4513,1706],{},[15,4515,4516],{},"正确顺序是“先复制共享字段到局部变量，再校验”。校验成功后只使用局部快照；校验失败则加悲观读锁重读。",[48,4518,4519],{"id":4519},"重要限制",[19,4521,4522,4525,4528,4531,4534],{},[22,4523,4524],{},"不可重入，同线程重复加同一锁可能自锁；",[22,4526,4527],{},"不支持 Condition；",[22,4529,4530],{},"stamp 必须与锁模式匹配；",[22,4532,4533],{},"乐观读不适合包含网络调用、数据库写入等不可逆副作用；",[22,4535,4536],{},"写频繁时校验经常失败，收益可能消失。",[15,4538,4539],{},"如果数据可以设计为不可变对象并通过 volatile 引用整体替换，通常比 StampedLock 更简单。",[10,4541,4543],{"id":4542},"九synchronized-与-reentrantlock","九、synchronized 与 ReentrantLock",[95,4545,4546,4556],{},[98,4547,4548],{},[101,4549,4550,4552,4554],{},[104,4551,3835],{},[104,4553,843],{},[104,4555,4035],{},[114,4557,4558,4569,4579,4590,4602,4614,4625],{},[101,4559,4560,4563,4566],{},[119,4561,4562],{},"释放",[119,4564,4565],{},"JVM 自动释放",[119,4567,4568],{},"必须 finally 手动释放",[101,4570,4571,4574,4577],{},[119,4572,4573],{},"可重入",[119,4575,4576],{},"支持",[119,4578,4576],{},[101,4580,4581,4584,4587],{},[119,4582,4583],{},"公平策略",[119,4585,4586],{},"不提供显式公平选项",[119,4588,4589],{},"支持公平\u002F非公平",[101,4591,4592,4595,4598],{},[119,4593,4594],{},"中断等待",[119,4596,4597],{},"不提供对应获取 API",[119,4599,4600],{},[195,4601,4287],{},[101,4603,4604,4607,4610],{},[119,4605,4606],{},"超时尝试",[119,4608,4609],{},"不支持",[119,4611,4612],{},[195,4613,4299],{},[101,4615,4616,4619,4622],{},[119,4617,4618],{},"条件队列",[119,4620,4621],{},"每个对象一个 Monitor 等待集合",[119,4623,4624],{},"一把锁可有多个 Condition",[101,4626,4627,4630,4633],{},[119,4628,4629],{},"实现",[119,4631,4632],{},"JVM Monitor 与运行时优化",[119,4634,4635],{},"AQS",[15,4637,4638],{},"不要简单说 ReentrantLock 一定更快。现代 JVM 对 synchronized 做了大量优化，性能取决于竞争程度、临界区和功能需求。",[10,4640,4642],{"id":4641},"十锁如何选","十、锁如何选",[48,4644,4646],{"id":4645},"优先-synchronized","优先 synchronized",[19,4648,4649,4652,4655,4658],{},[22,4650,4651],{},"普通互斥；",[22,4653,4654],{},"临界区结构简单；",[22,4656,4657],{},"不需要超时、中断、公平和多个条件队列；",[22,4659,4660],{},"希望异常时自动释放锁。",[48,4662,4664],{"id":4663},"选择-reentrantlock","选择 ReentrantLock",[19,4666,4667,4674,4677,4680],{},[22,4668,4669,4670,4673],{},"需要 ",[195,4671,4672],{},"tryLock","、超时或中断；",[22,4675,4676],{},"需要多个 Condition；",[22,4678,4679],{},"需要显式公平策略；",[22,4681,4682],{},"需要锁状态监控接口。",[48,4684,4686],{"id":4685},"选择读写锁或-stampedlock","选择读写锁或 StampedLock",[19,4688,4689,4692],{},[22,4690,4691],{},"读多写少，并已通过基准测试证明读并发收益明显；",[22,4693,4694],{},"StampedLock 仅用于低写冲突、短读取、能正确重试的场景。",[48,4696,4697],{"id":4697},"优先考虑无共享设计",[15,4699,4700],{},"锁不是唯一方案。不可变对象、线程封闭、分片、消息队列串行化、并发容器和原子类往往能减少锁复杂度。",[10,4702,4704],{"id":4703},"十一常见锁问题","十一、常见锁问题",[48,4706,4707],{"id":4707},"死锁",[15,4709,4710],{},"四个必要条件：互斥、请求并持有、不可剥夺、循环等待。治理方法包括固定加锁顺序、缩短临界区、避免嵌套锁和使用超时获取。",[48,4712,4713],{"id":4713},"活锁",[15,4715,4716],{},"线程不断响应彼此、状态一直变化，却没有实际进展。随机退避可以缓解部分活锁。",[48,4718,4719],{"id":4719},"饥饿",[15,4721,4722],{},"某线程长期得不到 CPU 或锁。公平策略可以缓解，但会牺牲一定吞吐。",[48,4724,4725],{"id":4725},"锁内慢操作",[15,4727,4728],{},"不要在锁中执行不可控远程调用、慢 SQL、文件 I\u002FO 或大计算。它们会把外部延迟放大为所有竞争线程的等待。",[10,4730,4732],{"id":4731},"十二高频面试问题","十二、高频面试问题",[292,4734,4735,4738,4741,4744,4747,4750,4753,4756,4759,4762],{},[22,4736,4737],{},"synchronized 锁实例方法和静态方法有什么区别？",[22,4739,4740],{},"Monitor 的 Owner、EntryList、WaitSet 分别是什么？",[22,4742,4743],{},"notify 后线程为什么不能立即执行？",[22,4745,4746],{},"synchronized 与 ReentrantLock 如何选择？",[22,4748,4749],{},"公平锁为什么吞吐通常更低？",[22,4751,4752],{},"Condition await\u002Fsignal 的流程是什么？",[22,4754,4755],{},"读写锁为什么不支持直接升级？",[22,4757,4758],{},"StampedLock 乐观读为什么必须先读再 validate？",[22,4760,4761],{},"偏向锁是否仍存在于所有现代 JDK？",[22,4763,4764],{},"如何排查 Java 死锁？",[10,4766,496],{"id":496},[19,4768,4769,4776,4782,4788],{},[22,4770,4771],{},[502,4772,4775],{"href":4773,"rel":4774},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Flocks\u002FLock.html",[506],"Java Lock 接口",[22,4777,4778],{},[502,4779,4035],{"href":4780,"rel":4781},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Flocks\u002FReentrantLock.html",[506],[22,4783,4784],{},[502,4785,4043],{"href":4786,"rel":4787},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Flocks\u002FStampedLock.html",[506],[22,4789,4790],{},[502,4791,4793],{"href":3243,"rel":4792},[506],"Java 语言规范：线程与锁",[1923,4795,1925],{},{"title":193,"searchDepth":530,"depth":530,"links":4797},[4798,4799,4800,4804,4809,4813,4814,4818,4821,4822,4828,4834,4835],{"id":4019,"depth":530,"text":4020},{"id":4047,"depth":530,"text":4048},{"id":4129,"depth":530,"text":4130,"children":4801},[4802,4803],{"id":4169,"depth":536,"text":4170},{"id":4179,"depth":536,"text":4180},{"id":4186,"depth":530,"text":4187,"children":4805},[4806,4807,4808],{"id":4205,"depth":536,"text":4205},{"id":4211,"depth":536,"text":4211},{"id":4217,"depth":536,"text":4217},{"id":4223,"depth":530,"text":4224,"children":4810},[4811,4812],{"id":4274,"depth":536,"text":4274},{"id":4312,"depth":536,"text":4312},{"id":4321,"depth":530,"text":4322},{"id":4374,"depth":530,"text":4375,"children":4815},[4816,4817],{"id":4390,"depth":536,"text":4391},{"id":4410,"depth":536,"text":4410},{"id":4428,"depth":530,"text":4429,"children":4819},[4820],{"id":4519,"depth":536,"text":4519},{"id":4542,"depth":530,"text":4543},{"id":4641,"depth":530,"text":4642,"children":4823},[4824,4825,4826,4827],{"id":4645,"depth":536,"text":4646},{"id":4663,"depth":536,"text":4664},{"id":4685,"depth":536,"text":4686},{"id":4697,"depth":536,"text":4697},{"id":4703,"depth":530,"text":4704,"children":4829},[4830,4831,4832,4833],{"id":4707,"depth":536,"text":4707},{"id":4713,"depth":536,"text":4713},{"id":4719,"depth":536,"text":4719},{"id":4725,"depth":536,"text":4725},{"id":4731,"depth":530,"text":4732},{"id":496,"depth":530,"text":496},"wiki:java:java-lock-mechanisms","建立 Java 锁的分类体系，理解 Monitor、ReentrantLock、读写锁、StampedLock 及其工程选型。",{},33,"\u002Fjava\u002Fjava-lock-mechanisms",{"title":901,"description":4837},"java\u002Fjava-lock-mechanisms","sYQXJRYZIAzkb-0Hi4iv71c0-60ZAzeU6SdJUaevRhI",{"id":4845,"title":907,"body":4846,"commentId":5565,"description":5566,"difficulty":566,"draft":567,"extension":568,"meta":5567,"navigation":570,"order":5568,"path":5569,"section":922,"seo":5570,"stem":5571,"updated":576,"__hash__":5572},"java\u002Fjava\u002Faqs-internals.md",{"type":7,"value":4847,"toc":5526},[4848,4852,4859,4882,4885,4891,4895,4898,4918,4925,4929,4974,4977,4981,4984,4990,4993,4999,5002,5008,5011,5015,5022,5028,5031,5034,5051,5054,5058,5061,5065,5068,5071,5088,5091,5095,5098,5102,5108,5123,5127,5130,5149,5155,5159,5162,5182,5185,5189,5192,5195,5198,5201,5204,5210,5213,5216,5220,5227,5233,5236,5239,5242,5252,5255,5258,5261,5265,5274,5280,5284,5290,5294,5297,5300,5304,5307,5310,5339,5346,5350,5353,5359,5363,5380,5383,5387,5390,5421,5424,5430,5434,5460,5464,5496,5498,5524],[10,4849,4851],{"id":4850},"一aqs-解决什么问题","一、AQS 解决什么问题",[15,4853,4854,4855,4858],{},"AQS 全称 ",[195,4856,4857],{},"AbstractQueuedSynchronizer","，是一套构建锁和同步器的框架。它把同步器的共同机制抽出来：",[19,4860,4861,4867,4870,4873,4879],{},[22,4862,1775,4863,4866],{},[195,4864,4865],{},"state"," 表示同步状态；",[22,4868,4869],{},"用 FIFO 等待队列管理竞争失败的线程；",[22,4871,4872],{},"使用 CAS 修改状态和队列；",[22,4874,316,4875,4878],{},[195,4876,4877],{},"LockSupport.park\u002Funpark"," 阻塞和唤醒；",[22,4880,4881],{},"支持独占模式、共享模式和 Condition。",[15,4883,4884],{},"子类主要定义“什么条件下获取或释放状态”，AQS 负责失败后的排队、阻塞和传播。",[15,4886,4887],{},[3329,4888],{"alt":4889,"src":4890},"AQS 核心数据结构：state、同步队列与等待节点","\u002Fimages\u002Fwiki\u002Fjava\u002Faqs-core-structure.svg",[10,4892,4894],{"id":4893},"二aqs-的核心字段","二、AQS 的核心字段",[15,4896,4897],{},"概念上最重要的是：",[187,4899,4901],{"className":1315,"code":4900,"language":1317,"meta":193,"style":193},"private volatile int state;\nprivate transient volatile Node head;\nprivate transient volatile Node tail;\n",[195,4902,4903,4908,4913],{"__ignoreMap":193},[1321,4904,4905],{"class":1323,"line":660},[1321,4906,4907],{},"private volatile int state;\n",[1321,4909,4910],{"class":1323,"line":530},[1321,4911,4912],{},"private transient volatile Node head;\n",[1321,4914,4915],{"class":1323,"line":536},[1321,4916,4917],{},"private transient volatile Node tail;\n",[15,4919,4920,4921,4924],{},"不同 JDK 版本的 Node 字段和状态常量可能调整，但 ",[195,4922,4923],{},"state + FIFO 同步队列"," 这条主线稳定。",[48,4926,4928],{"id":4927},"state-的语义由子类解释","state 的语义由子类解释",[95,4930,4931,4941],{},[98,4932,4933],{},[101,4934,4935,4938],{},[104,4936,4937],{},"同步器",[104,4939,4940],{},"state 的含义",[114,4942,4943,4950,4958,4966],{},[101,4944,4945,4947],{},[119,4946,4035],{},[119,4948,4949],{},"0 表示空闲，正数表示独占锁重入次数",[101,4951,4952,4955],{},[119,4953,4954],{},"Semaphore",[119,4956,4957],{},"剩余许可证数",[101,4959,4960,4963],{},[119,4961,4962],{},"CountDownLatch",[119,4964,4965],{},"尚未归零的计数",[101,4967,4968,4971],{},[119,4969,4970],{},"ReentrantReadWriteLock",[119,4972,4973],{},"高低位组合记录读写锁状态",[15,4975,4976],{},"AQS 不知道 state 具体代表什么，它只提供原子读写和排队框架。",[10,4978,4980],{"id":4979},"三模板方法设计","三、模板方法设计",[15,4982,4983],{},"独占模式常由子类实现：",[187,4985,4988],{"className":4986,"code":4987,"language":192,"meta":193},[190],"tryAcquire\ntryRelease\nisHeldExclusively\n",[195,4989,4987],{"__ignoreMap":193},[15,4991,4992],{},"共享模式常由子类实现：",[187,4994,4997],{"className":4995,"code":4996,"language":192,"meta":193},[190],"tryAcquireShared\ntryReleaseShared\n",[195,4998,4996],{"__ignoreMap":193},[15,5000,5001],{},"AQS 的公开\u002F最终方法负责完整流程：",[187,5003,5006],{"className":5004,"code":5005,"language":192,"meta":193},[190],"先尝试获取\n失败则入队\n在合适时机重试\n仍失败则 park\n释放时唤醒后继\n",[195,5007,5005],{"__ignoreMap":193},[15,5009,5010],{},"这就是模板方法模式：框架固定流程，子类填入同步策略。",[10,5012,5014],{"id":5013},"四独占获取流程","四、独占获取流程",[15,5016,5017,5018,5021],{},"以 ",[195,5019,5020],{},"ReentrantLock.lock()"," 为例，概念流程是：",[187,5023,5026],{"className":5024,"code":5025,"language":192,"meta":193},[190],"tryAcquire\n  ├─ 成功：设置持有线程并返回\n  └─ 失败：创建等待节点\n              ↓\n           追加到队尾\n              ↓\n     前驱是 head 时再次尝试\n              ↓\n        仍失败则 park\n              ↓\n        被唤醒后循环重试\n",[195,5027,5025],{"__ignoreMap":193},[48,5029,5030],{"id":5030},"为什么入队后还要循环重试",[15,5032,5033],{},"线程可能因为以下原因醒来：",[19,5035,5036,5042,5045,5048],{},[22,5037,5038,5039,313],{},"前驱释放锁并 ",[195,5040,5041],{},"unpark",[22,5043,5044],{},"中断；",[22,5046,5047],{},"虚假唤醒；",[22,5049,5050],{},"超时版本到期。",[15,5052,5053],{},"因此唤醒不等于已经获得锁，必须回到循环重新检查条件。",[48,5055,5057],{"id":5056},"为什么通常由-head-的直接后继竞争","为什么通常由 head 的直接后继竞争",[15,5059,5060],{},"让队首附近节点优先尝试，可以维持大体 FIFO，减少所有等待线程同时抢锁造成的惊群。",[10,5062,5064],{"id":5063},"五aqs-等待队列是什么","五、AQS 等待队列是什么",[15,5066,5067],{},"AQS 的同步队列常被描述为 CLH 队列的变体。它不是最原始的“所有线程在前驱状态上纯自旋”的 CLH 锁，而是维护显式前后链接，并在需要时阻塞线程。",[15,5069,5070],{},"典型节点包含概念信息：",[19,5072,5073,5076,5079,5082,5085],{},[22,5074,5075],{},"当前等待线程；",[22,5077,5078],{},"前驱、后继链接；",[22,5080,5081],{},"等待状态；",[22,5083,5084],{},"独占或共享模式；",[22,5086,5087],{},"Condition 队列链接。",[15,5089,5090],{},"head 通常是已经成功获取同步状态的节点或哨兵位置。新节点通过 CAS 竞争 tail 加入队尾。",[48,5092,5094],{"id":5093},"为什么入队使用-cas","为什么入队使用 CAS",[15,5096,5097],{},"多个线程可能同时入队，CAS 可以让一个线程成功更新 tail，失败线程重新读取队尾再尝试，避免用一把全局锁保护队列追加。",[10,5099,5101],{"id":5100},"六阻塞与唤醒","六、阻塞与唤醒",[15,5103,5104,5105,3740],{},"AQS 使用 ",[195,5106,5107],{},"LockSupport",[187,5109,5111],{"className":1315,"code":5110,"language":1317,"meta":193,"style":193},"LockSupport.park(this);\nLockSupport.unpark(thread);\n",[195,5112,5113,5118],{"__ignoreMap":193},[1321,5114,5115],{"class":1323,"line":660},[1321,5116,5117],{},"LockSupport.park(this);\n",[1321,5119,5120],{"class":1323,"line":530},[1321,5121,5122],{},"LockSupport.unpark(thread);\n",[48,5124,5126],{"id":5125},"permit-模型","permit 模型",[15,5128,5129],{},"每个线程最多拥有一个 permit：",[19,5131,5132,5140,5143,5146],{},[22,5133,5134,5136,5137,313],{},[195,5135,5041],{}," 可以先于 ",[195,5138,5139],{},"park",[22,5141,5142],{},"permit 不会无限累积；",[22,5144,5145],{},"有 permit 时下一次 park 可以直接返回；",[22,5147,5148],{},"park 允许虚假返回，所以必须循环检查条件。",[15,5150,5151,5152,5154],{},"相比 ",[195,5153,4359],{},"，LockSupport 不要求调用方先持有某个对象 Monitor，并可精确指定唤醒线程。",[10,5156,5158],{"id":5157},"七释放独占锁","七、释放独占锁",[15,5160,5161],{},"ReentrantLock 释放时大致是：",[292,5163,5164,5170,5173,5176,5179],{},[22,5165,5166,5169],{},[195,5167,5168],{},"tryRelease"," 减少 state；",[22,5171,5172],{},"state 归零时清除独占持有线程；",[22,5174,5175],{},"检查同步队列；",[22,5177,5178],{},"唤醒合适的后继节点；",[22,5180,5181],{},"被唤醒线程重新执行获取循环。",[15,5183,5184],{},"可重入锁每次成功重入都会增加 state，因此必须对应释放相同次数，state 归零才真正释放所有权。",[10,5186,5188],{"id":5187},"八公平锁与非公平锁","八、公平锁与非公平锁",[48,5190,5191],{"id":5191},"非公平获取",[15,5193,5194],{},"新线程可以先直接 CAS 抢 state。即使队列中已有等待线程，它仍可能插队成功。",[15,5196,5197],{},"优点是减少线程切换、提高吞吐；缺点是等待时间方差更大，可能发生饥饿。",[48,5199,5200],{"id":5200},"公平获取",[15,5202,5203],{},"获取前通常检查：",[187,5205,5208],{"className":5206,"code":5207,"language":192,"meta":193},[190],"hasQueuedPredecessors()\n",[195,5209,5207],{"__ignoreMap":193},[15,5211,5212],{},"如果前面已有等待节点，就不插队，进入或留在队列中。",[15,5214,5215],{},"公平锁只是倾向按等待顺序授予锁，不是严格实时调度保证。",[10,5217,5219],{"id":5218},"九共享模式","九、共享模式",[15,5221,5222,5223,5226],{},"共享模式允许多个线程同时成功获取状态。",[195,5224,5225],{},"tryAcquireShared"," 的返回值通常表达：",[187,5228,5231],{"className":5229,"code":5230,"language":192,"meta":193},[190],"负数：获取失败\n零：获取成功，但没有剩余共享能力\n正数：获取成功，后继节点还可能继续获取\n",[195,5232,5230],{"__ignoreMap":193},[48,5234,4954],{"id":5235},"semaphore",[15,5237,5238],{},"state 表示许可证数量。获取许可证减少 state，释放增加 state。只要还有许可证，多个线程就能同时通过。",[48,5240,4962],{"id":5241},"countdownlatch",[15,5243,5244,5245,5247,5248,5251],{},"state 表示剩余计数。",[195,5246,4345],{}," 在 state 不为 0 时进入共享等待；",[195,5249,5250],{},"countDown()"," 递减，归零后传播唤醒所有等待线程。",[15,5253,5254],{},"CountDownLatch 一次性使用，归零后不能重置。",[48,5256,4039],{"id":5257},"reentrantreadwritelockreadlock",[15,5259,5260],{},"读锁使用共享模式，多个读线程可以同时成功；写锁存在时读获取失败。",[10,5262,5264],{"id":5263},"十condition-的双队列模型","十、Condition 的双队列模型",[15,5266,5267,5268,5270,5271,5273],{},"Condition 不是直接在同步队列中等待。调用 ",[195,5269,4345],{}," 的线程先进入 Condition 队列，并完全释放锁；收到 ",[195,5272,4349],{}," 后，节点被转移到 AQS 同步队列，再重新竞争锁。",[15,5275,5276],{},[3329,5277],{"alt":5278,"src":5279},"Condition await 与 signal 的队列转移流程","\u002Fimages\u002Fwiki\u002Fjava\u002Faqs-condition-transfer.svg",[48,5281,5283],{"id":5282},"await-流程","await 流程",[187,5285,5288],{"className":5286,"code":5287,"language":192,"meta":193},[190],"线程必须持有独占锁\n  ↓\n加入 Condition 队列\n  ↓\n完全释放当前重入锁\n  ↓\npark 等待\n  ↓\n被 signal 后进入同步队列\n  ↓\n重新获取原来的锁\n  ↓\n恢复 await 前的重入次数\n  ↓\nawait 返回\n",[195,5289,5287],{"__ignoreMap":193},[48,5291,5293],{"id":5292},"signal-流程","signal 流程",[15,5295,5296],{},"调用 signal 的线程也必须持有锁。signal 把 Condition 队列中等待最久的有效节点转移到同步队列，并不会把锁直接交给它。",[15,5298,5299],{},"当前 signal 线程退出临界区并释放锁后，被转移节点才有机会获取锁。",[10,5301,5303],{"id":5302},"十一中断取消与超时","十一、中断、取消与超时",[15,5305,5306],{},"等待线程可能因为中断、超时或异常取消。队列必须跳过已取消节点，并保证后继仍有机会被唤醒。",[15,5308,5309],{},"不同 API 的中断语义不同：",[19,5311,5312,5317,5322,5327,5333],{},[22,5313,5314,5316],{},[195,5315,4281],{}," 获取过程中通常不因中断而抛出，但会保留\u002F恢复中断状态；",[22,5318,5319,5321],{},[195,5320,4287],{}," 等待时响应中断并退出；",[22,5323,5324,5326],{},[195,5325,4299],{}," 同时处理超时与中断；",[22,5328,5329,5332],{},[195,5330,5331],{},"Condition.await()"," 可响应中断；",[22,5334,5335,5338],{},[195,5336,5337],{},"awaitUninterruptibly()"," 不因中断提前返回。",[15,5340,5341,5342,5345],{},"工程中必须清楚调用的是哪一种语义，不能捕获 ",[195,5343,5344],{},"InterruptedException"," 后直接忽略。",[10,5347,5349],{"id":5348},"十二aqs-与-reentrantlock-的关系","十二、AQS 与 ReentrantLock 的关系",[15,5351,5352],{},"ReentrantLock 外层负责 Lock API，内部 Sync 继承 AQS：",[187,5354,5357],{"className":5355,"code":5356,"language":192,"meta":193},[190],"ReentrantLock\n  ↓ 委托\nSync extends AQS\n  ├─ NonfairSync\n  └─ FairSync\n",[195,5358,5356],{"__ignoreMap":193},[48,5360,5362],{"id":5361},"state-与-owner","state 与 owner",[19,5364,5365,5368,5371,5374,5377],{},[22,5366,5367],{},"state 为 0：锁空闲；",[22,5369,5370],{},"首次获取：CAS 把 state 从 0 改为 1，并设置独占线程；",[22,5372,5373],{},"同线程重入：state 增加；",[22,5375,5376],{},"unlock：state 减少；",[22,5378,5379],{},"state 归零：清除 owner，唤醒后继。",[15,5381,5382],{},"只比较 state 不足以实现可重入，还必须判断当前线程是否就是独占持有者。",[10,5384,5386],{"id":5385},"十三如何阅读-aqs-源码","十三、如何阅读 AQS 源码",[15,5388,5389],{},"不要从所有字段开始背。建议按一条真实调用链：",[292,5391,5392,5396,5402,5405,5408,5415,5418],{},[22,5393,5394,313],{},[195,5395,5020],{},[22,5397,5398,5399,313],{},"公平或非公平 ",[195,5400,5401],{},"tryAcquire",[22,5403,5404],{},"AQS 获取失败后的入队；",[22,5406,5407],{},"park 与 unpark；",[22,5409,5410,639,5413,313],{},[195,5411,5412],{},"unlock()",[195,5414,5168],{},[22,5416,5417],{},"再看共享模式；",[22,5419,5420],{},"最后看 Condition 的双队列转移。",[15,5422,5423],{},"阅读时区分三个层次：",[187,5425,5428],{"className":5426,"code":5427,"language":192,"meta":193},[190],"同步策略：子类如何解释 state\n排队机制：AQS 如何管理失败线程\n线程调度：LockSupport 如何 park\u002Funpark\n",[195,5429,5427],{"__ignoreMap":193},[10,5431,5433],{"id":5432},"十四常见误区","十四、常见误区",[292,5435,5436,5439,5442,5445,5448,5451,5454,5457],{},[22,5437,5438],{},"AQS 不是锁，而是构建锁和同步器的框架；",[22,5440,5441],{},"入队成功不代表获得锁；",[22,5443,5444],{},"unpark 不代表线程一定立即运行或获得锁；",[22,5446,5447],{},"signal 不会让 Condition 线程直接执行；",[22,5449,5450],{},"公平锁不是操作系统级严格公平调度；",[22,5452,5453],{},"Semaphore 控制并发数量，不等同于严格 QPS 限流；",[22,5455,5456],{},"CountDownLatch 使用共享模式，但它不是可重复屏障；",[22,5458,5459],{},"AQS 队列是 CLH 思想的工程变体，不是纯自旋 CLH 的原样实现。",[10,5461,5463],{"id":5462},"十五高频面试问题","十五、高频面试问题",[292,5465,5466,5469,5472,5475,5478,5481,5484,5487,5490,5493],{},[22,5467,5468],{},"AQS 的 state 和同步队列分别做什么？",[22,5470,5471],{},"子类为什么只需实现 tryAcquire\u002FtryRelease？",[22,5473,5474],{},"获取锁失败后如何入队和阻塞？",[22,5476,5477],{},"为什么线程被唤醒后还要重新竞争？",[22,5479,5480],{},"公平锁与非公平锁在获取路径上有什么区别？",[22,5482,5483],{},"AQS 独占模式和共享模式有什么区别？",[22,5485,5486],{},"Condition 为什么需要单独的等待队列？",[22,5488,5489],{},"signal 后节点如何从 Condition 队列进入同步队列？",[22,5491,5492],{},"ReentrantLock 如何通过 state 实现重入？",[22,5494,5495],{},"CountDownLatch 和 Semaphore 如何解释 state？",[10,5497,496],{"id":496},[19,5499,5500,5506,5513,5519],{},[22,5501,5502],{},[502,5503,4857],{"href":5504,"rel":5505},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Flocks\u002FAbstractQueuedSynchronizer.html",[506],[22,5507,5508],{},[502,5509,5512],{"href":5510,"rel":5511},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Flocks\u002FAbstractQueuedSynchronizer.ConditionObject.html",[506],"AQS ConditionObject",[22,5514,5515],{},[502,5516,5107],{"href":5517,"rel":5518},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F25\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002Flocks\u002FLockSupport.html",[506],[22,5520,5521],{},[502,5522,4035],{"href":4780,"rel":5523},[506],[1923,5525,1925],{},{"title":193,"searchDepth":530,"depth":530,"links":5527},[5528,5529,5532,5533,5537,5540,5543,5544,5548,5553,5557,5558,5561,5562,5563,5564],{"id":4850,"depth":530,"text":4851},{"id":4893,"depth":530,"text":4894,"children":5530},[5531],{"id":4927,"depth":536,"text":4928},{"id":4979,"depth":530,"text":4980},{"id":5013,"depth":530,"text":5014,"children":5534},[5535,5536],{"id":5030,"depth":536,"text":5030},{"id":5056,"depth":536,"text":5057},{"id":5063,"depth":530,"text":5064,"children":5538},[5539],{"id":5093,"depth":536,"text":5094},{"id":5100,"depth":530,"text":5101,"children":5541},[5542],{"id":5125,"depth":536,"text":5126},{"id":5157,"depth":530,"text":5158},{"id":5187,"depth":530,"text":5188,"children":5545},[5546,5547],{"id":5191,"depth":536,"text":5191},{"id":5200,"depth":536,"text":5200},{"id":5218,"depth":530,"text":5219,"children":5549},[5550,5551,5552],{"id":5235,"depth":536,"text":4954},{"id":5241,"depth":536,"text":4962},{"id":5257,"depth":536,"text":4039},{"id":5263,"depth":530,"text":5264,"children":5554},[5555,5556],{"id":5282,"depth":536,"text":5283},{"id":5292,"depth":536,"text":5293},{"id":5302,"depth":530,"text":5303},{"id":5348,"depth":530,"text":5349,"children":5559},[5560],{"id":5361,"depth":536,"text":5362},{"id":5385,"depth":530,"text":5386},{"id":5432,"depth":530,"text":5433},{"id":5462,"depth":530,"text":5463},{"id":496,"depth":530,"text":496},"wiki:java:aqs-internals","沿着获取、入队、阻塞、唤醒和条件等待主线，理解 AbstractQueuedSynchronizer。",{},34,"\u002Fjava\u002Faqs-internals",{"title":907,"description":5566},"java\u002Faqs-internals","-0yvnFCImeOUWDEXKBx4XeqAJVIJZuMiLQHFXA9xiI8",{"id":5574,"title":1016,"body":5575,"commentId":6103,"description":6104,"difficulty":566,"draft":567,"extension":568,"meta":6105,"navigation":570,"order":6106,"path":6107,"section":1030,"seo":6108,"stem":6109,"updated":576,"__hash__":6110},"java\u002Fjava\u002Fspring-source-code.md",{"type":7,"value":5576,"toc":6078},[5577,5581,5584,5595,5598,5602,5611,5617,5621,5624,5641,5644,5647,5653,5658,5661,5666,5670,5673,5679,5682,5685,5688,5730,5734,5737,5743,5746,5752,5755,5758,5761,5775,5779,5782,5788,5792,5811,5814,5851,5854,5860,5875,5878,5882,5889,5895,5898,5930,5938,5941,5951,5960,5963,5966,5989,5993,5996,6039,6043,6046,6051,6053,6076],[10,5578,5580],{"id":5579},"为什么要沿主线读-spring-源码","为什么要沿主线读 Spring 源码",[15,5582,5583],{},"Spring 源码类很多，但核心问题只有三个：",[292,5585,5586,5589,5592],{},[22,5587,5588],{},"配置如何变成 BeanDefinition；",[22,5590,5591],{},"Bean 如何被实例化、注入、初始化并增强；",[22,5593,5594],{},"一次方法调用如何经过 AOP 和事务拦截器。",[15,5596,5597],{},"先理解调用链，再记扩展点和类名，比从某个实现类逐行背源码有效得多。",[10,5599,5601],{"id":5600},"一ioc-容器启动主线","一、IoC 容器启动主线",[15,5603,5604,5606,5607,5610],{},[195,5605,1005],{}," 启动的核心入口是 ",[195,5608,5609],{},"refresh()","。可以把它理解成一次容器重建：准备环境、创建 BeanFactory、注册扩展点、实例化单例 Bean，最后发布启动完成事件。",[187,5612,5615],{"className":5613,"code":5614,"language":192,"meta":193},[190],"准备环境\n  ↓\n创建并配置 BeanFactory\n  ↓\n加载 BeanDefinition\n  ↓\n执行 BeanFactoryPostProcessor\n  ↓\n注册 BeanPostProcessor\n  ↓\n初始化事件、国际化等基础设施\n  ↓\n实例化剩余非懒加载单例 Bean\n  ↓\n发布 ContextRefreshedEvent\n",[195,5616,5614],{"__ignoreMap":193},[48,5618,5620],{"id":5619},"beandefinition-是什么","BeanDefinition 是什么",[15,5622,5623],{},"BeanDefinition 是 Bean 的“配方”，而不是 Bean 实例。它保存：",[19,5625,5626,5629,5632,5635,5638],{},[22,5627,5628],{},"Bean 类型；",[22,5630,5631],{},"scope、lazy、primary 等配置；",[22,5633,5634],{},"构造参数和属性依赖；",[22,5636,5637],{},"初始化与销毁方法；",[22,5639,5640],{},"工厂方法等元数据。",[15,5642,5643],{},"XML、注解和 Java Config 最终都要转成 BeanDefinition，注册到 BeanFactory 中。",[48,5645,5646],{"id":5646},"两类重要后置处理器",[15,5648,5649,5652],{},[195,5650,5651],{},"BeanFactoryPostProcessor"," 处理的是 BeanDefinition，可以在 Bean 实例化前修改元数据。典型实现包括配置类解析器和属性占位符处理器。",[15,5654,5655,5657],{},[195,5656,955],{}," 处理的是 Bean 实例，可以在初始化前后修改或包装 Bean。AOP 自动代理就是在这条扩展链上完成的。",[15,5659,5660],{},"一句话区分：",[489,5662,5663],{},[15,5664,5665],{},"BeanFactoryPostProcessor 改“配方”，BeanPostProcessor 改“成品”。",[10,5667,5669],{"id":5668},"二bean-创建与生命周期","二、Bean 创建与生命周期",[15,5671,5672],{},"单例 Bean 会依次经历实例化、依赖注入、初始化增强、对外提供服务和销毁。完整流程如下：",[15,5674,5675],{},[3329,5676],{"alt":5677,"src":5678},"Spring Bean 生命周期：从 BeanDefinition 到销毁回调","\u002Fimages\u002Fwiki\u002Fjava\u002Fspring-bean-lifecycle.svg",[15,5680,5681],{},"需要注意，“实例化”只是创建对象，“初始化”还包括依赖注入、生命周期回调和代理增强。",[48,5683,5684],{"id":5684},"常见生命周期回调顺序",[15,5686,5687],{},"常见顺序是：",[292,5689,5690,5693,5696,5706,5716,5721,5727],{},[22,5691,5692],{},"构造对象；",[22,5694,5695],{},"注入属性；",[22,5697,5698,5699,306,5702,5705],{},"执行 ",[195,5700,5701],{},"BeanNameAware",[195,5703,5704],{},"BeanFactoryAware"," 等回调；",[22,5707,5708,5709,5712,5713,313],{},"执行初始化前的 BeanPostProcessor，其中 ",[195,5710,5711],{},"CommonAnnotationBeanPostProcessor"," 会触发 ",[195,5714,5715],{},"@PostConstruct",[22,5717,5698,5718,313],{},[195,5719,5720],{},"InitializingBean.afterPropertiesSet()",[22,5722,5723,5724,313],{},"执行自定义 ",[195,5725,5726],{},"init-method",[22,5728,5729],{},"执行初始化后的 BeanPostProcessor，AOP 代理常在这里生成。",[10,5731,5733],{"id":5732},"三三级缓存与循环依赖","三、三级缓存与循环依赖",[15,5735,5736],{},"Spring 单例创建相关的三个缓存通常是：",[187,5738,5741],{"className":5739,"code":5740,"language":192,"meta":193},[190],"singletonObjects：完整初始化后的单例\nearlySingletonObjects：已提前暴露的早期引用\nsingletonFactories：能够生成早期引用的 ObjectFactory\n",[195,5742,5740],{"__ignoreMap":193},[15,5744,5745],{},"以 A、B 通过字段或 setter 相互依赖为例：",[15,5747,5748],{},[3329,5749],{"alt":5750,"src":5751},"Spring 三级缓存解决单例 Bean 循环依赖流程","\u002Fimages\u002Fwiki\u002Fjava\u002Fspring-circular-dependency.svg",[15,5753,5754],{},"三级缓存的关键价值不只是“提前拿到对象”，还在于能够通过工厂生成早期代理引用，尽量保证依赖方拿到的对象与最终代理对象一致。",[48,5756,5757],{"id":5757},"解决边界",[15,5759,5760],{},"Spring 不能解决所有循环依赖：",[19,5762,5763,5766,5769,5772],{},[22,5764,5765],{},"构造器循环依赖通常无法解决，因为对象还没实例化就已经需要对方；",[22,5767,5768],{},"prototype Bean 不使用单例缓存，因此不能依赖这套机制；",[22,5770,5771],{},"部分代理、异步初始化或自定义后置处理器可能让早期引用与最终对象不一致；",[22,5773,5774],{},"最好的方案通常仍是重新划分职责，而不是依赖循环引用。",[10,5776,5778],{"id":5777},"四aop-代理是怎样产生的","四、AOP 代理是怎样产生的",[15,5780,5781],{},"Spring AOP 的主线是：找到 Advisor，判断 Bean 是否命中切点，然后用自动代理创建器包装 Bean。",[187,5783,5786],{"className":5784,"code":5785,"language":192,"meta":193},[190],"Bean 初始化\n  ↓\nAbstractAutoProxyCreator 后置处理\n  ↓\n查找匹配的 Advisor\n  ↓\n创建 JDK 动态代理或 CGLIB 代理\n  ↓\n调用代理方法\n  ↓\n执行 MethodInterceptor 链\n  ↓\n调用目标方法\n",[195,5787,5785],{"__ignoreMap":193},[48,5789,5791],{"id":5790},"jdk-动态代理与-cglib","JDK 动态代理与 CGLIB",[19,5793,5794,5797,5800,5808],{},[22,5795,5796],{},"JDK 动态代理基于接口生成代理；",[22,5798,5799],{},"CGLIB 通过生成目标类的子类实现代理；",[22,5801,5802,5804,5805,5807],{},[195,5803,2658],{}," 类不能被继承，",[195,5806,2658],{}," 方法不能被覆盖，因此不能按普通方式被 CGLIB 增强；",[22,5809,5810],{},"业务代码应依赖抽象，但代理方式不应成为架构设计的核心目标。",[48,5812,5813],{"id":5813},"为什么自调用会导致切面失效",[187,5815,5817],{"className":1315,"code":5816,"language":1317,"meta":193,"style":193},"public void outer() {\n    inner();\n}\n\n@Transactional\npublic void inner() {\n}\n",[195,5818,5819,5824,5829,5833,5837,5842,5847],{"__ignoreMap":193},[1321,5820,5821],{"class":1323,"line":660},[1321,5822,5823],{},"public void outer() {\n",[1321,5825,5826],{"class":1323,"line":530},[1321,5827,5828],{},"    inner();\n",[1321,5830,5831],{"class":1323,"line":536},[1321,5832,1706],{},[1321,5834,5835],{"class":1323,"line":1028},[1321,5836,2082],{"emptyLinePlaceholder":570},[1321,5838,5839],{"class":1323,"line":1126},[1321,5840,5841],{},"@Transactional\n",[1321,5843,5844],{"class":1323,"line":1199},[1321,5845,5846],{},"public void inner() {\n",[1321,5848,5849],{"class":1323,"line":2095},[1321,5850,1706],{},[15,5852,5853],{},"外部调用通常是：",[187,5855,5858],{"className":5856,"code":5857,"language":192,"meta":193},[190],"调用方 → 代理对象 → 拦截器 → 目标对象\n",[195,5859,5857],{"__ignoreMap":193},[15,5861,5862,5863,5866,5867,5870,5871,5874],{},"而 ",[195,5864,5865],{},"outer()"," 内部的 ",[195,5868,5869],{},"inner()"," 是目标对象上的 ",[195,5872,5873],{},"this.inner()","，没有再次经过代理，因此事务、缓存、异步等基于代理的增强可能不生效。",[15,5876,5877],{},"更稳妥的做法是拆分 Bean，让调用跨越代理边界；不要把“从容器中获取自己”作为默认方案。",[10,5879,5881],{"id":5880},"五声明式事务源码主线","五、声明式事务源码主线",[15,5883,5884,5885,5888],{},"声明式事务本质上是一个 AOP Advisor。方法调用进入 ",[195,5886,5887],{},"TransactionInterceptor"," 后，根据事务属性决定开启、加入或挂起事务，再调用目标方法，最后提交或回滚。",[187,5890,5893],{"className":5891,"code":5892,"language":192,"meta":193},[190],"调用代理方法\n  ↓\nTransactionInterceptor\n  ↓\n读取 @Transactional 属性\n  ↓\nPlatformTransactionManager 获取事务\n  ↓\n执行目标方法\n  ├─ 正常：提交\n  └─ 异常：按回滚规则回滚\n",[195,5894,5892],{"__ignoreMap":193},[48,5896,5897],{"id":5897},"传播行为重点",[19,5899,5900,5906,5912,5918,5924],{},[22,5901,5902,5905],{},[195,5903,5904],{},"REQUIRED","：有事务就加入，没有就新建，默认值；",[22,5907,5908,5911],{},[195,5909,5910],{},"REQUIRES_NEW","：挂起外层事务，创建独立事务；",[22,5913,5914,5917],{},[195,5915,5916],{},"NESTED","：通常基于保存点，仍属于同一个物理事务；",[22,5919,5920,5923],{},[195,5921,5922],{},"SUPPORTS","：有事务就加入，没有则非事务执行；",[22,5925,5926,5929],{},[195,5927,5928],{},"NOT_SUPPORTED","：挂起现有事务，非事务执行。",[15,5931,5932,5934,5935,5937],{},[195,5933,5910],{}," 与 ",[195,5936,5916],{}," 不等价：前者是独立事务，内层提交后不随外层回滚；后者一般共享外层物理事务，只能回滚到保存点。",[48,5939,5940],{"id":5940},"默认回滚规则",[15,5942,5943,5944,639,5947,5950],{},"Spring 默认对 ",[195,5945,5946],{},"RuntimeException",[195,5948,5949],{},"Error"," 回滚，对受检异常默认不回滚。需要时显式配置：",[187,5952,5954],{"className":1315,"code":5953,"language":1317,"meta":193,"style":193},"@Transactional(rollbackFor = Exception.class)\n",[195,5955,5956],{"__ignoreMap":193},[1321,5957,5958],{"class":1323,"line":660},[1321,5959,5953],{},[15,5961,5962],{},"不要为了触发回滚而吞掉异常。如果 catch 后既不重新抛出，也不手动标记回滚，事务拦截器会把方法视为正常完成。",[48,5964,5965],{"id":5965},"常见失效场景",[292,5967,5968,5971,5974,5977,5980,5983,5986],{},[22,5969,5970],{},"同类自调用绕过代理；",[22,5972,5973],{},"Bean 没有被 Spring 容器管理；",[22,5975,5976],{},"方法无法被当前代理方式增强；",[22,5978,5979],{},"异常被捕获后未继续抛出；",[22,5981,5982],{},"数据库表或连接不支持事务；",[22,5984,5985],{},"新线程、异步任务不继承当前线程的事务上下文；",[22,5987,5988],{},"事务范围过大，在事务中执行远程调用或长时间计算。",[10,5990,5992],{"id":5991},"六源码阅读路线","六、源码阅读路线",[15,5994,5995],{},"推荐依次跟踪：",[292,5997,5998,6004,6010,6016,6022,6028,6034],{},[22,5999,6000,6003],{},[195,6001,6002],{},"AbstractApplicationContext.refresh()","：容器启动总流程；",[22,6005,6006,6009],{},[195,6007,6008],{},"DefaultListableBeanFactory","：BeanDefinition 注册与依赖查找；",[22,6011,6012,6015],{},[195,6013,6014],{},"AbstractAutowireCapableBeanFactory.doCreateBean()","：Bean 创建；",[22,6017,6018,6021],{},[195,6019,6020],{},"DefaultSingletonBeanRegistry","：单例缓存；",[22,6023,6024,6027],{},[195,6025,6026],{},"AbstractAutoProxyCreator","：自动代理；",[22,6029,6030,6033],{},[195,6031,6032],{},"JdkDynamicAopProxy"," \u002F CGLIB 拦截器：方法调用；",[22,6035,6036,6038],{},[195,6037,5887],{},"：事务边界。",[10,6040,6042],{"id":6041},"七面试回答主线","七、面试回答主线",[15,6044,6045],{},"被问“Spring 如何创建一个带事务的 Bean”时，可以这样组织：",[489,6047,6048],{},[15,6049,6050],{},"容器先把配置解析为 BeanDefinition，通过 BeanFactory 创建 Bean，完成实例化、依赖注入和初始化回调。BeanPostProcessor 会在初始化前后介入，事务基础设施中的自动代理创建器判断该 Bean 是否命中事务 Advisor，命中后返回代理对象。外部调用代理方法时进入 TransactionInterceptor，由事务管理器创建或加入事务，再执行目标方法，并按异常和回滚规则提交或回滚。",[10,6052,496],{"id":496},[19,6054,6055,6062,6069],{},[22,6056,6057],{},[502,6058,6061],{"href":6059,"rel":6060},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fcore\u002Fbeans\u002Fdefinition.html",[506],"Spring BeanDefinition 官方文档",[22,6063,6064],{},[502,6065,6068],{"href":6066,"rel":6067},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fcore\u002Fbeans\u002Fdependencies\u002Ffactory-collaborators.html",[506],"Spring 依赖注入与循环依赖",[22,6070,6071],{},[502,6072,6075],{"href":6073,"rel":6074},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fdata-access\u002Ftransaction\u002Fdeclarative.html",[506],"Spring 声明式事务",[1923,6077,1925],{},{"title":193,"searchDepth":530,"depth":530,"links":6079},[6080,6081,6085,6088,6091,6095,6100,6101,6102],{"id":5579,"depth":530,"text":5580},{"id":5600,"depth":530,"text":5601,"children":6082},[6083,6084],{"id":5619,"depth":536,"text":5620},{"id":5646,"depth":536,"text":5646},{"id":5668,"depth":530,"text":5669,"children":6086},[6087],{"id":5684,"depth":536,"text":5684},{"id":5732,"depth":530,"text":5733,"children":6089},[6090],{"id":5757,"depth":536,"text":5757},{"id":5777,"depth":530,"text":5778,"children":6092},[6093,6094],{"id":5790,"depth":536,"text":5791},{"id":5813,"depth":536,"text":5813},{"id":5880,"depth":530,"text":5881,"children":6096},[6097,6098,6099],{"id":5897,"depth":536,"text":5897},{"id":5940,"depth":536,"text":5940},{"id":5965,"depth":536,"text":5965},{"id":5991,"depth":530,"text":5992},{"id":6041,"depth":530,"text":6042},{"id":496,"depth":530,"text":496},"wiki:java:spring-source-code","沿着容器启动、Bean 生命周期、代理创建和事务拦截四条主线理解 Spring 源码。",{},41,"\u002Fjava\u002Fspring-source-code",{"title":1016,"description":6104},"java\u002Fspring-source-code","swwE3bVi_S3n1MRhykBlZ06XwesMtmv9NFDSGw3OpQ4",{"id":6112,"title":1104,"body":6113,"commentId":6648,"description":6649,"difficulty":566,"draft":567,"extension":568,"meta":6650,"navigation":570,"order":6651,"path":6652,"section":1128,"seo":6653,"stem":6654,"updated":576,"__hash__":6655},"java\u002Fjava\u002Fmysql-core-interview.md",{"type":7,"value":6114,"toc":6615},[6115,6119,6122,6128,6131,6135,6138,6152,6155,6158,6164,6167,6170,6174,6181,6203,6206,6209,6235,6238,6242,6245,6248,6252,6279,6282,6286,6289,6303,6306,6309,6313,6317,6320,6324,6327,6331,6334,6337,6354,6361,6365,6368,6374,6376,6400,6403,6407,6411,6414,6418,6421,6424,6427,6430,6433,6437,6440,6465,6468,6471,6474,6480,6483,6486,6489,6506,6509,6513,6542,6545,6549,6581,6583,6613],[10,6116,6118],{"id":6117},"一先建立-innodb-全景图","一、先建立 InnoDB 全景图",[15,6120,6121],{},"一次更新大致会同时影响：",[187,6123,6126],{"className":6124,"code":6125,"language":192,"meta":193},[190],"SQL 层解析与优化\n  ↓\nInnoDB Buffer Pool 中的数据页\n  ↓\nundo log 保存旧版本\n  ↓\nredo log 保证崩溃恢复\n  ↓\nbinlog 记录逻辑变更\n  ↓\n后台线程把脏页刷盘\n",[195,6127,6125],{"__ignoreMap":193},[15,6129,6130],{},"理解 MySQL 不应只背索引。索引决定“怎么找到数据”，MVCC 和锁决定“并发时看见什么、能修改什么”，日志决定“失败后如何恢复”。",[10,6132,6134],{"id":6133},"二为什么-innodb-使用-btree","二、为什么 InnoDB 使用 B+Tree",[15,6136,6137],{},"B+Tree 适合磁盘和页式存储：",[19,6139,6140,6143,6146,6149],{},[22,6141,6142],{},"非叶子节点只保存索引信息，一个页可容纳更多键，树高更低；",[22,6144,6145],{},"叶子节点按键有序并相互连接，范围扫描高效；",[22,6147,6148],{},"单次查找通常只需很少的页访问；",[22,6150,6151],{},"等值、排序、范围查询都能复用同一结构。",[48,6153,6154],{"id":6154},"聚簇索引与二级索引",[15,6156,6157],{},"InnoDB 的主键索引是聚簇索引，叶子节点保存完整行数据。二级索引叶子节点保存索引列和主键值。",[187,6159,6162],{"className":6160,"code":6161,"language":192,"meta":193},[190],"二级索引查找\n  ↓\n得到主键\n  ↓\n回到聚簇索引查完整行（回表）\n",[195,6163,6161],{"__ignoreMap":193},[15,6165,6166],{},"如果查询所需列都包含在索引中，就可以使用覆盖索引，避免回表。",[15,6168,6169],{},"主键应尽量短、稳定、递增。过长主键会被每个二级索引重复存储；频繁随机插入可能增加页分裂和缓存局部性问题。",[10,6171,6173],{"id":6172},"三联合索引与最左匹配","三、联合索引与最左匹配",[15,6175,6176,6177,6180],{},"联合索引 ",[195,6178,6179],{},"(a, b, c)"," 按三列组合排序，通常可支持：",[187,6182,6186],{"className":6183,"code":6184,"language":6185,"meta":193,"style":193},"language-sql shiki shiki-themes github-light github-dark","WHERE a = ?\nWHERE a = ? AND b = ?\nWHERE a = ? AND b = ? AND c = ?\n","sql",[195,6187,6188,6193,6198],{"__ignoreMap":193},[1321,6189,6190],{"class":1323,"line":660},[1321,6191,6192],{},"WHERE a = ?\n",[1321,6194,6195],{"class":1323,"line":530},[1321,6196,6197],{},"WHERE a = ? AND b = ?\n",[1321,6199,6200],{"class":1323,"line":536},[1321,6201,6202],{},"WHERE a = ? AND b = ? AND c = ?\n",[15,6204,6205],{},"能否使用后续列，要结合查询条件和优化器判断。遇到范围条件后，后续列通常不能继续用于缩小同一段扫描范围，但仍可能用于索引条件下推、覆盖或排序，不能简单背成“范围后索引全部失效”。",[48,6207,6208],{"id":6208},"常见索引效果变差的原因",[19,6210,6211,6214,6217,6220,6226,6229,6232],{},[22,6212,6213],{},"对索引列做函数或隐式类型转换；",[22,6215,6216],{},"联合索引缺少关键前缀；",[22,6218,6219],{},"低选择性条件返回数据过多；",[22,6221,6222,6223,313],{},"前导模糊匹配，如 ",[195,6224,6225],{},"LIKE '%abc'",[22,6227,6228],{},"OR 两侧缺少合适索引；",[22,6230,6231],{},"统计信息不准，优化器估算错误；",[22,6233,6234],{},"查询本身需要返回大部分表数据，全表扫描反而更便宜。",[15,6236,6237],{},"“写了索引”不代表一定使用，“使用了索引”也不代表查询一定快。",[10,6239,6241],{"id":6240},"四mvcc-与-read-view","四、MVCC 与 Read View",[15,6243,6244],{},"InnoDB 通过隐藏事务信息、undo log 版本链和 Read View 实现一致性非锁定读。",[15,6246,6247],{},"可以把 Read View 理解成一个可见性规则集合：当前事务读取某行时，根据该行版本的事务 ID 判断它是否可见；不可见就沿 undo 版本链寻找更早版本。",[48,6249,6251],{"id":6250},"rc-与-rr-的核心差异","RC 与 RR 的核心差异",[19,6253,6254,6260,6266],{},[22,6255,6256,6259],{},[195,6257,6258],{},"READ COMMITTED"," 通常每次一致性读都创建新的 Read View，因此同一事务两次查询可能看到其他事务新提交的数据；",[22,6261,6262,6265],{},[195,6263,6264],{},"REPEATABLE READ"," 通常在第一次一致性读时建立快照，后续一致性读复用它，因此普通快照读可以重复读取同一视图；",[22,6267,6268,6269,306,6272,306,6275,6278],{},"当前读，如 ",[195,6270,6271],{},"SELECT ... FOR UPDATE",[195,6273,6274],{},"UPDATE",[195,6276,6277],{},"DELETE","，要读取可锁定的当前版本，不能用旧快照替代。",[15,6280,6281],{},"MVCC 主要优化读写并发，不意味着“所有读都不加锁”。",[10,6283,6285],{"id":6284},"五事务隔离与并发现象","五、事务隔离与并发现象",[15,6287,6288],{},"常见并发现象：",[19,6290,6291,6294,6297,6300],{},[22,6292,6293],{},"脏读：读到未提交数据；",[22,6295,6296],{},"不可重复读：同一行两次读取结果不同；",[22,6298,6299],{},"幻读：同一条件两次查询，结果集合出现或消失记录；",[22,6301,6302],{},"丢失更新：并发更新互相覆盖。",[15,6304,6305],{},"MySQL InnoDB 默认通常是 RR。RR 下，快照读通过 MVCC 提供可重复读；锁定范围的当前读可使用 next-key lock 抑制幻行插入。",[15,6307,6308],{},"隔离级别越高不等于业务一定越安全。库存、额度、余额等场景仍需明确使用条件更新、锁或版本号。",[10,6310,6312],{"id":6311},"六innodb-的锁","六、InnoDB 的锁",[48,6314,6316],{"id":6315},"record-lock","Record Lock",[15,6318,6319],{},"锁定索引记录。InnoDB 的“行锁”本质上通常落在索引记录上。",[48,6321,6323],{"id":6322},"gap-lock","Gap Lock",[15,6325,6326],{},"锁定索引记录之间的间隙，主要用于防止其他事务在范围中插入新记录。",[48,6328,6330],{"id":6329},"next-key-lock","Next-Key Lock",[15,6332,6333],{},"Record Lock 与其前方 Gap Lock 的组合，用于锁定一个范围。",[15,6335,6336],{},"锁的实际范围取决于：",[19,6338,6339,6342,6345,6348,6351],{},[22,6340,6341],{},"隔离级别；",[22,6343,6344],{},"是否命中唯一索引；",[22,6346,6347],{},"查询条件和实际执行计划；",[22,6349,6350],{},"扫描到的索引记录；",[22,6352,6353],{},"是否属于锁定读或写操作。",[15,6355,6356,6357,6360],{},"因此不要只看 SQL 的 ",[195,6358,6359],{},"WHERE"," 条件猜锁范围，必须结合索引和执行计划。",[10,6362,6364],{"id":6363},"七死锁如何产生与治理","七、死锁如何产生与治理",[15,6366,6367],{},"典型死锁是不同事务以不同顺序持有并等待资源：",[187,6369,6372],{"className":6370,"code":6371,"language":192,"meta":193},[190],"事务 A：锁订单 1 → 等订单 2\n事务 B：锁订单 2 → 等订单 1\n",[195,6373,6371],{"__ignoreMap":193},[15,6375,290],{},[292,6377,6378,6381,6384,6387,6390,6393],{},[22,6379,6380],{},"多表、多行更新保持固定顺序；",[22,6382,6383],{},"缩短事务，不在事务中做远程调用；",[22,6385,6386],{},"建立合适索引，减少无关记录扫描与加锁；",[22,6388,6389],{},"单次批量不要过大；",[22,6391,6392],{},"应用必须捕获死锁异常并做有限重试；",[22,6394,6395,6396,6399],{},"通过 ",[195,6397,6398],{},"SHOW ENGINE INNODB STATUS","、错误日志和性能视图分析死锁链。",[15,6401,6402],{},"死锁不是简单“提高隔离级别”就能消除，写操作之间仍可能形成循环等待。",[10,6404,6406],{"id":6405},"八redoundo-与-binlog","八、redo、undo 与 binlog",[48,6408,6410],{"id":6409},"undo-log","undo log",[15,6412,6413],{},"保存修改前的逻辑信息，用于事务回滚和 MVCC 版本读取。长事务会阻碍旧版本清理，导致 undo 膨胀。",[48,6415,6417],{"id":6416},"redo-log","redo log",[15,6419,6420],{},"InnoDB 的物理\u002F页级恢复日志。更新先修改内存页并记录 redo，之后再异步刷脏页，借助 WAL 降低随机写并支持崩溃恢复。",[48,6422,6423],{"id":6423},"binlog",[15,6425,6426],{},"MySQL Server 层的逻辑变更日志，主要用于复制、审计和时间点恢复。",[48,6428,6429],{"id":6429},"两阶段提交",[15,6431,6432],{},"一次事务同时涉及 redo log 和 binlog。MySQL 通过 prepare\u002Fcommit 协调两类日志，避免崩溃后出现“存储引擎认为提交、binlog 却没有”或相反的状态。",[10,6434,6436],{"id":6435},"九高并发资金更新如何保证安全","九、高并发资金更新如何保证安全",[48,6438,6439],{"id":6439},"条件更新",[187,6441,6443],{"className":6183,"code":6442,"language":6185,"meta":193,"style":193},"UPDATE account\nSET available = available - :amount\nWHERE account_id = :id\n  AND available >= :amount;\n",[195,6444,6445,6450,6455,6460],{"__ignoreMap":193},[1321,6446,6447],{"class":1323,"line":660},[1321,6448,6449],{},"UPDATE account\n",[1321,6451,6452],{"class":1323,"line":530},[1321,6453,6454],{},"SET available = available - :amount\n",[1321,6456,6457],{"class":1323,"line":536},[1321,6458,6459],{},"WHERE account_id = :id\n",[1321,6461,6462],{"class":1323,"line":1028},[1321,6463,6464],{},"  AND available >= :amount;\n",[15,6466,6467],{},"根据受影响行数判断是否成功，可以把校验和扣减放入一个原子 SQL，避免“先查余额再更新”的竞态。",[48,6469,6470],{"id":6470},"幂等唯一键",[15,6472,6473],{},"对业务请求号、凭证号建立唯一索引：",[187,6475,6478],{"className":6476,"code":6477,"language":192,"meta":193},[190],"业务幂等号唯一约束\n  ↓\n同一请求重复提交\n  ↓\n数据库拒绝第二次插入\n",[195,6479,6477],{"__ignoreMap":193},[15,6481,6482],{},"唯一键是资金系统非常重要的最后一道防线，但仍需明确冲突后的业务返回语义。",[48,6484,6485],{"id":6485},"热点账户",[15,6487,6488],{},"大量请求更新同一余额行时，瓶颈往往是行锁串行、锁等待和连接占用。可根据实时性要求采用：",[19,6490,6491,6494,6497,6500,6503],{},[22,6492,6493],{},"同账户请求串行化；",[22,6495,6496],{},"异步聚合、批量入账；",[22,6498,6499],{},"凭证先落库，余额异步更新；",[22,6501,6502],{},"拆分业务账户或分段计数；",[22,6504,6505],{},"限制事务持锁时间。",[15,6507,6508],{},"不能为了吞吐简单把一个强一致余额随机拆成多个值，必须同时设计汇总和一致性规则。",[10,6510,6512],{"id":6511},"十慢-sql-排查顺序","十、慢 SQL 排查顺序",[292,6514,6515,6518,6527,6530,6533,6536,6539],{},[22,6516,6517],{},"获取真实 SQL、参数、耗时分布和调用频率；",[22,6519,6520,6521,1779,6524,313],{},"看 ",[195,6522,6523],{},"EXPLAIN",[195,6525,6526],{},"EXPLAIN ANALYZE",[22,6528,6529],{},"关注访问类型、实际扫描行数、过滤率、选中索引和 Extra；",[22,6531,6532],{},"检查索引选择性、数据分布与统计信息；",[22,6534,6535],{},"检查锁等待、事务时长和连接池；",[22,6537,6538],{},"判断瓶颈是 CPU、磁盘 I\u002FO、网络返回量还是锁；",[22,6540,6541],{},"修改后用相同数据规模和参数复测。",[15,6543,6544],{},"不要看到慢查询就先加索引。索引会增加写放大、存储占用和维护成本。",[10,6546,6548],{"id":6547},"十一高频面试问题","十一、高频面试问题",[292,6550,6551,6554,6557,6560,6563,6566,6569,6572,6575,6578],{},[22,6552,6553],{},"聚簇索引和二级索引有什么区别？",[22,6555,6556],{},"联合索引为什么遵循最左匹配？",[22,6558,6559],{},"覆盖索引和索引条件下推分别解决什么问题？",[22,6561,6562],{},"RC 与 RR 的 Read View 有何差异？",[22,6564,6565],{},"快照读与当前读有什么区别？",[22,6567,6568],{},"Record、Gap、Next-Key Lock 如何配合？",[22,6570,6571],{},"为什么有索引仍可能慢？",[22,6573,6574],{},"redo log、undo log、binlog 分别解决什么问题？",[22,6576,6577],{},"两阶段提交为什么必要？",[22,6579,6580],{},"账户余额如何防止超扣、重复入账和热点锁竞争？",[10,6582,496],{"id":496},[19,6584,6585,6592,6599,6606],{},[22,6586,6587],{},[502,6588,6591],{"href":6589,"rel":6590},"https:\u002F\u002Fdev.mysql.com\u002Fdoc\u002Frefman\u002F8.4\u002Fen\u002Finnodb-introduction.html",[506],"InnoDB 简介与聚簇索引",[22,6593,6594],{},[502,6595,6598],{"href":6596,"rel":6597},"https:\u002F\u002Fdev.mysql.com\u002Fdoc\u002Frefman\u002F8.4\u002Fen\u002Finnodb-locking-transaction-model.html",[506],"InnoDB 锁与事务模型",[22,6600,6601],{},[502,6602,6605],{"href":6603,"rel":6604},"https:\u002F\u002Fdev.mysql.com\u002Fdoc\u002Frefman\u002F8.4\u002Fen\u002Finnodb-transaction-isolation-levels.html",[506],"InnoDB 事务隔离级别",[22,6607,6608],{},[502,6609,6612],{"href":6610,"rel":6611},"https:\u002F\u002Fdev.mysql.com\u002Fdoc\u002Frefman\u002F8.4\u002Fen\u002Finnodb-deadlocks.html",[506],"InnoDB 死锁",[1923,6614,1925],{},{"title":193,"searchDepth":530,"depth":530,"links":6616},[6617,6618,6621,6624,6627,6628,6633,6634,6640,6645,6646,6647],{"id":6117,"depth":530,"text":6118},{"id":6133,"depth":530,"text":6134,"children":6619},[6620],{"id":6154,"depth":536,"text":6154},{"id":6172,"depth":530,"text":6173,"children":6622},[6623],{"id":6208,"depth":536,"text":6208},{"id":6240,"depth":530,"text":6241,"children":6625},[6626],{"id":6250,"depth":536,"text":6251},{"id":6284,"depth":530,"text":6285},{"id":6311,"depth":530,"text":6312,"children":6629},[6630,6631,6632],{"id":6315,"depth":536,"text":6316},{"id":6322,"depth":536,"text":6323},{"id":6329,"depth":536,"text":6330},{"id":6363,"depth":530,"text":6364},{"id":6405,"depth":530,"text":6406,"children":6635},[6636,6637,6638,6639],{"id":6409,"depth":536,"text":6410},{"id":6416,"depth":536,"text":6417},{"id":6423,"depth":536,"text":6423},{"id":6429,"depth":536,"text":6429},{"id":6435,"depth":530,"text":6436,"children":6641},[6642,6643,6644],{"id":6439,"depth":536,"text":6439},{"id":6470,"depth":536,"text":6470},{"id":6485,"depth":536,"text":6485},{"id":6511,"depth":530,"text":6512},{"id":6547,"depth":530,"text":6548},{"id":496,"depth":530,"text":496},"wiki:java:mysql-core-interview","从 InnoDB 存储结构到 MVCC、锁、日志和慢 SQL，建立可用于项目与面试的 MySQL 主线。",{},51,"\u002Fjava\u002Fmysql-core-interview",{"title":1104,"description":6649},"java\u002Fmysql-core-interview","s6jYL1SzknlW6w4Q2CoapgGbyhvWManpsVNb5FBlAC0",{"id":4,"title":5,"body":6657,"commentId":564,"description":565,"difficulty":566,"draft":567,"extension":568,"meta":7028,"navigation":570,"order":571,"path":572,"section":573,"seo":7029,"stem":575,"updated":576,"__hash__":577},{"type":7,"value":6658,"toc":6995},[6659,6661,6663,6677,6679,6681,6683,6685,6687,6689,6691,6693,6695,6697,6699,6701,6703,6705,6707,6745,6747,6749,6751,6753,6755,6757,6759,6761,6767,6769,6771,6776,6778,6780,6790,6792,6794,6796,6810,6814,6816,6818,6823,6825,6827,6829,6831,6833,6838,6840,6866,6872,6874,6876,6878,6880,6882,6896,6898,6900,6902,6904,6906,6914,6916,6918,6923,6931,6933,6938,6940,6942,6944,6949,6951,6953,6963,6965,6967,6971,6973],[10,6660,13],{"id":12},[15,6662,17],{},[19,6664,6665,6667,6669,6671,6673,6675],{},[22,6666,24],{},[22,6668,27],{},[22,6670,30],{},[22,6672,33],{},[22,6674,36],{},[22,6676,39],{},[15,6678,42],{},[10,6680,46],{"id":45},[48,6682,51],{"id":50},[15,6684,54],{},[48,6686,58],{"id":57},[15,6688,61],{},[48,6690,65],{"id":64},[15,6692,68],{},[48,6694,72],{"id":71},[15,6696,75],{},[48,6698,79],{"id":78},[15,6700,82],{},[48,6702,86],{"id":85},[15,6704,89],{},[10,6706,93],{"id":92},[95,6708,6709,6719],{},[98,6710,6711],{},[101,6712,6713,6715,6717],{},[104,6714,106],{},[104,6716,109],{},[104,6718,112],{},[114,6720,6721,6729,6737],{},[101,6722,6723,6725,6727],{},[119,6724,121],{},[119,6726,124],{},[119,6728,127],{},[101,6730,6731,6733,6735],{},[119,6732,132],{},[119,6734,135],{},[119,6736,138],{},[101,6738,6739,6741,6743],{},[119,6740,143],{},[119,6742,146],{},[119,6744,149],{},[48,6746,121],{"id":121},[15,6748,154],{},[15,6750,157],{},[48,6752,132],{"id":132},[15,6754,162],{},[15,6756,165],{},[48,6758,143],{"id":143},[15,6760,170],{},[19,6762,6763,6765],{},[22,6764,175],{},[22,6766,178],{},[10,6768,182],{"id":181},[15,6770,185],{},[187,6772,6774],{"className":6773,"code":191,"language":192,"meta":193},[190],[195,6775,191],{"__ignoreMap":193},[15,6777,199],{},[15,6779,202],{},[19,6781,6782,6784,6786,6788],{},[22,6783,207],{},[22,6785,210],{},[22,6787,213],{},[22,6789,216],{},[15,6791,219],{},[10,6793,223],{"id":222},[15,6795,226],{},[19,6797,6798,6800,6802,6804,6808],{},[22,6799,231],{},[22,6801,234],{},[22,6803,237],{},[22,6805,240,6806,244],{},[195,6807,243],{},[22,6809,247],{},[15,6811,6812,253],{},[195,6813,252],{},[48,6815,256],{"id":256},[15,6817,259],{},[187,6819,6821],{"className":6820,"code":263,"language":192,"meta":193},[190],[195,6822,263],{"__ignoreMap":193},[15,6824,268],{},[48,6826,271],{"id":271},[15,6828,274],{},[10,6830,278],{"id":277},[15,6832,281],{},[187,6834,6836],{"className":6835,"code":285,"language":192,"meta":193},[190],[195,6837,285],{"__ignoreMap":193},[15,6839,290],{},[292,6841,6842,6844,6846,6854,6862],{},[22,6843,296],{},[22,6845,299],{},[22,6847,302,6848,306,6850,306,6852,313],{},[195,6849,305],{},[195,6851,309],{},[195,6853,312],{},[22,6855,316,6856,306,6858,306,6860,326],{},[195,6857,319],{},[195,6859,322],{},[195,6861,325],{},[22,6863,329,6864,333],{},[195,6865,332],{},[15,6867,6868,338,6870,342],{},[195,6869,332],{},[195,6871,341],{},[10,6873,346],{"id":345},[48,6875,350],{"id":349},[15,6877,353],{},[48,6879,357],{"id":356},[15,6881,360],{},[19,6883,6884,6888,6892],{},[22,6885,6886,368],{},[195,6887,367],{},[22,6889,6890,374],{},[195,6891,373],{},[22,6893,6894,380],{},[195,6895,379],{},[15,6897,383],{},[48,6899,386],{"id":386},[15,6901,389],{},[10,6903,393],{"id":392},[15,6905,396],{},[19,6907,6908,6910,6912],{},[22,6909,401],{},[22,6911,404],{},[22,6913,407],{},[48,6915,411],{"id":410},[15,6917,414],{},[187,6919,6921],{"className":6920,"code":418,"language":192,"meta":193},[190],[195,6922,418],{"__ignoreMap":193},[15,6924,423,6925,427,6927,431,6929,435],{},[195,6926,426],{},[195,6928,430],{},[195,6930,434],{},[15,6932,438],{},[187,6934,6936],{"className":6935,"code":442,"language":192,"meta":193},[190],[195,6937,442],{"__ignoreMap":193},[15,6939,447],{},[10,6941,451],{"id":450},[15,6943,454],{},[187,6945,6947],{"className":6946,"code":458,"language":192,"meta":193},[190],[195,6948,458],{"__ignoreMap":193},[15,6950,463],{},[15,6952,466],{},[19,6954,6955,6957,6959,6961],{},[22,6956,471],{},[22,6958,474],{},[22,6960,477],{},[22,6962,480],{},[15,6964,483],{},[10,6966,487],{"id":486},[489,6968,6969],{},[15,6970,493],{},[10,6972,496],{"id":496},[19,6974,6975,6980,6985,6990],{},[22,6976,6977],{},[502,6978,507],{"href":504,"rel":6979},[506],[22,6981,6982],{},[502,6983,514],{"href":512,"rel":6984},[506],[22,6986,6987],{},[502,6988,521],{"href":519,"rel":6989},[506],[22,6991,6992],{},[502,6993,528],{"href":526,"rel":6994},[506],{"title":193,"searchDepth":530,"depth":530,"links":6996},[6997,6998,7006,7011,7012,7016,7017,7022,7025,7026,7027],{"id":12,"depth":530,"text":13},{"id":45,"depth":530,"text":46,"children":6999},[7000,7001,7002,7003,7004,7005],{"id":50,"depth":536,"text":51},{"id":57,"depth":536,"text":58},{"id":64,"depth":536,"text":65},{"id":71,"depth":536,"text":72},{"id":78,"depth":536,"text":79},{"id":85,"depth":536,"text":86},{"id":92,"depth":530,"text":93,"children":7007},[7008,7009,7010],{"id":121,"depth":536,"text":121},{"id":132,"depth":536,"text":132},{"id":143,"depth":536,"text":143},{"id":181,"depth":530,"text":182},{"id":222,"depth":530,"text":223,"children":7013},[7014,7015],{"id":256,"depth":536,"text":256},{"id":271,"depth":536,"text":271},{"id":277,"depth":530,"text":278},{"id":345,"depth":530,"text":346,"children":7018},[7019,7020,7021],{"id":349,"depth":536,"text":350},{"id":356,"depth":536,"text":357},{"id":386,"depth":536,"text":386},{"id":392,"depth":530,"text":393,"children":7023},[7024],{"id":410,"depth":536,"text":411},{"id":450,"depth":530,"text":451},{"id":486,"depth":530,"text":487},{"id":496,"depth":530,"text":496},{},{"title":5,"description":565},{"id":7031,"title":1115,"body":7032,"commentId":7606,"description":7607,"difficulty":566,"draft":567,"extension":568,"meta":7608,"navigation":570,"order":7609,"path":7610,"section":573,"seo":7611,"stem":7612,"updated":576,"__hash__":7613},"java\u002Fjava\u002Fkafka-mq-reliability.md",{"type":7,"value":7033,"toc":7575},[7034,7038,7041,7047,7050,7055,7058,7062,7082,7089,7093,7097,7117,7120,7137,7140,7143,7146,7150,7153,7156,7162,7170,7173,7177,7180,7186,7189,7192,7196,7199,7205,7215,7292,7296,7299,7302,7307,7310,7313,7338,7341,7345,7348,7352,7355,7361,7368,7371,7374,7378,7381,7387,7390,7394,7397,7403,7406,7410,7413,7417,7420,7426,7429,7432,7436,7439,7445,7448,7462,7465,7469,7472,7478,7481,7504,7507,7511,7514,7517,7531,7535,7540,7542,7572],[10,7035,7037],{"id":7036},"一kafka-的核心模型","一、Kafka 的核心模型",[15,7039,7040],{},"Kafka 的逻辑层次是：",[187,7042,7045],{"className":7043,"code":7044,"language":192,"meta":193},[190],"Topic\n  ↓\nPartition（有序追加日志）\n  ↓\nReplica（Leader + Followers）\n",[195,7046,7044],{"__ignoreMap":193},[15,7048,7049],{},"生产者向分区 Leader 写入，消费者从分区拉取。消费者组内，一个分区同一时刻最多分配给一个消费者实例，因此：",[489,7051,7052],{},[15,7053,7054],{},"消费组内的有效并行度受分区数上限约束。",[15,7056,7057],{},"不同消费者组各自维护消费进度，可以独立消费同一 Topic。",[10,7059,7061],{"id":7060},"二为什么-kafka-吞吐高","二、为什么 Kafka 吞吐高",[19,7063,7064,7067,7070,7073,7076,7079],{},[22,7065,7066],{},"顺序追加日志，减少随机 I\u002FO；",[22,7068,7069],{},"批量发送和压缩，提高网络与磁盘利用率；",[22,7071,7072],{},"页缓存充分利用操作系统缓存；",[22,7074,7075],{},"分区允许横向并行；",[22,7077,7078],{},"拉取模型让消费者按自身能力批量消费；",[22,7080,7081],{},"网络协议和数据传输路径针对批处理优化。",[15,7083,7084,7085,7088],{},"吞吐高不代表单条消息一定延迟最低，也不代表可靠性配置可以省略。",[195,7086,7087],{},"linger.ms","、batch、压缩和 ACK 都是在延迟、吞吐与可靠性之间取舍。",[10,7090,7092],{"id":7091},"三生产端如何保证可靠","三、生产端如何保证可靠",[48,7094,7096],{"id":7095},"ack","ACK",[19,7098,7099,7105,7111],{},[22,7100,7101,7104],{},[195,7102,7103],{},"acks=0","：不等待确认，吞吐高但可能静默丢失；",[22,7106,7107,7110],{},[195,7108,7109],{},"acks=1","：Leader 写入即确认，Leader 故障时未同步副本的数据可能丢失；",[22,7112,7113,7116],{},[195,7114,7115],{},"acks=all","：等待 ISR 中满足要求的副本确认，可靠性更高。",[15,7118,7119],{},"通常还应配合：",[19,7121,7122,7128,7131,7134],{},[22,7123,7124,7125,313],{},"合理的 ",[195,7126,7127],{},"min.insync.replicas",[22,7129,7130],{},"禁止不受控的非同步副本选主；",[22,7132,7133],{},"发送失败重试和回调告警；",[22,7135,7136],{},"业务级超时与降级。",[48,7138,7139],{"id":7139},"幂等生产者",[15,7141,7142],{},"幂等生产者使用 Producer ID、Epoch 和分区内序列号识别重试产生的重复写入，使单个生产者会话在单分区内的重试不会重复落日志。",[15,7144,7145],{},"它解决的是生产者到 Kafka 的重复写入，不等于解决消费者重复执行业务，也不等于跨系统 exactly-once。",[10,7147,7149],{"id":7148},"四broker-如何保证副本可靠","四、Broker 如何保证副本可靠",[15,7151,7152],{},"每个分区由 Leader 接收读写，Follower 从 Leader 同步。ISR 表示当前与 Leader 保持同步的副本集合。",[15,7154,7155],{},"可靠性需要组合配置：",[187,7157,7160],{"className":7158,"code":7159,"language":192,"meta":193},[190],"副本数 >= 3\nacks = all\nmin.insync.replicas >= 2\n合理的选主策略\n",[195,7161,7159],{"__ignoreMap":193},[15,7163,7164,7165,7167,7168,1713],{},"如果只设置 ",[195,7166,7115],{},"，但 ISR 中只剩 Leader，一个副本确认仍可能满足要求；所以必须同时理解 ",[195,7169,7127],{},[15,7171,7172],{},"副本复制通常是异步推进的，Kafka 在可用性、吞吐和数据安全之间做工程权衡，并非跨机房强一致数据库。",[10,7174,7176],{"id":7175},"五消费端为什么常见至少一次","五、消费端为什么常见“至少一次”",[15,7178,7179],{},"典型安全顺序是：",[187,7181,7184],{"className":7182,"code":7183,"language":192,"meta":193},[190],"拉取消息\n  ↓\n执行业务\n  ↓\n业务成功后提交 offset\n",[195,7185,7183],{"__ignoreMap":193},[15,7187,7188],{},"如果业务成功后、offset 提交前进程崩溃，重启后会再次消费，因此是至少一次语义。",[15,7190,7191],{},"如果先提交 offset 再执行业务，进程随后崩溃，则消息可能永久跳过。因此关键业务通常选择“先处理再提交 + 业务幂等”。",[48,7193,7195],{"id":7194},"kafka-消息有没有业务-messageid","Kafka 消息有没有业务 messageId",[15,7197,7198],{},"Kafka 原生定位一条日志记录的身份通常是：",[187,7200,7203],{"className":7201,"code":7202,"language":192,"meta":193},[190],"topic + partition + offset\n",[195,7204,7202],{"__ignoreMap":193},[15,7206,7207,7208,2195,7211,7214],{},"Kafka 不会自动为业务生成通用 ",[195,7209,7210],{},"messageId",[195,7212,7213],{},"eventId","。如果需要跨 Topic、重试 Topic、数据库和日志统一追踪，应由业务在消息信封中生成：",[187,7216,7220],{"className":7217,"code":7218,"language":7219,"meta":193,"style":193},"language-json shiki shiki-themes github-light github-dark","{\n  \"eventId\": \"uuid\",\n  \"eventType\": \"BILL_CONFIRMED\",\n  \"aggregateId\": \"bill-1001\",\n  \"occurredAt\": \"2026-07-16T10:00:00+08:00\",\n  \"payload\": {}\n}\n","json",[195,7221,7222,7228,7244,7256,7268,7280,7288],{"__ignoreMap":193},[1321,7223,7224],{"class":1323,"line":660},[1321,7225,7227],{"class":7226},"sVt8B","{\n",[1321,7229,7230,7234,7237,7241],{"class":1323,"line":530},[1321,7231,7233],{"class":7232},"sj4cs","  \"eventId\"",[1321,7235,7236],{"class":7226},": ",[1321,7238,7240],{"class":7239},"sZZnC","\"uuid\"",[1321,7242,7243],{"class":7226},",\n",[1321,7245,7246,7249,7251,7254],{"class":1323,"line":536},[1321,7247,7248],{"class":7232},"  \"eventType\"",[1321,7250,7236],{"class":7226},[1321,7252,7253],{"class":7239},"\"BILL_CONFIRMED\"",[1321,7255,7243],{"class":7226},[1321,7257,7258,7261,7263,7266],{"class":1323,"line":1028},[1321,7259,7260],{"class":7232},"  \"aggregateId\"",[1321,7262,7236],{"class":7226},[1321,7264,7265],{"class":7239},"\"bill-1001\"",[1321,7267,7243],{"class":7226},[1321,7269,7270,7273,7275,7278],{"class":1323,"line":1126},[1321,7271,7272],{"class":7232},"  \"occurredAt\"",[1321,7274,7236],{"class":7226},[1321,7276,7277],{"class":7239},"\"2026-07-16T10:00:00+08:00\"",[1321,7279,7243],{"class":7226},[1321,7281,7282,7285],{"class":1323,"line":1199},[1321,7283,7284],{"class":7232},"  \"payload\"",[1321,7286,7287],{"class":7226},": {}\n",[1321,7289,7290],{"class":1323,"line":2095},[1321,7291,1706],{"class":7226},[10,7293,7295],{"id":7294},"六消费幂等怎么做","六、消费幂等怎么做",[15,7297,7298],{},"常见方案：",[48,7300,7301],{"id":7301},"唯一键或幂等表",[15,7303,5017,7304,7306],{},[195,7305,7213],{}," 或业务请求号建立唯一索引，在同一个数据库事务中记录消费和更新业务数据。重复消息触发唯一键冲突后返回已处理结果。",[48,7308,7309],{"id":7309},"业务状态机",[15,7311,7312],{},"使用条件更新限制合法状态迁移：",[187,7314,7316],{"className":6183,"code":7315,"language":6185,"meta":193,"style":193},"UPDATE bill\nSET status = 'PAID'\nWHERE id = :id\n  AND status = 'PAYING';\n",[195,7317,7318,7323,7328,7333],{"__ignoreMap":193},[1321,7319,7320],{"class":1323,"line":660},[1321,7321,7322],{},"UPDATE bill\n",[1321,7324,7325],{"class":1323,"line":530},[1321,7326,7327],{},"SET status = 'PAID'\n",[1321,7329,7330],{"class":1323,"line":536},[1321,7331,7332],{},"WHERE id = :id\n",[1321,7334,7335],{"class":1323,"line":1028},[1321,7336,7337],{},"  AND status = 'PAYING';\n",[15,7339,7340],{},"重复执行时受影响行数为 0，不会重复推进。",[48,7342,7344],{"id":7343},"redis-去重","Redis 去重",[15,7346,7347],{},"适合可容忍最终一致、丢失风险可控的非核心场景。资金和结算不能只依赖有 TTL 的 Redis Key 作为最终幂等依据。",[10,7349,7351],{"id":7350},"七kafka-事务与-exactly-once-的边界","七、Kafka 事务与 exactly-once 的边界",[15,7353,7354],{},"Kafka 事务可以把多分区写入和消费 offset 提交放进同一个 Kafka 事务，典型用于：",[187,7356,7359],{"className":7357,"code":7358,"language":192,"meta":193},[190],"消费 Kafka A\n  ↓\n处理\n  ↓\n写入 Kafka B\n  +\n提交 A 的 offset\n",[195,7360,7358],{"__ignoreMap":193},[15,7362,7363,7364,7367],{},"下游使用 ",[195,7365,7366],{},"read_committed"," 时只读取已提交事务消息。",[15,7369,7370],{},"但这类 exactly-once 主要覆盖 Kafka 内部的“读—处理—写”。如果处理中还写 MySQL、调用支付接口或发送 HTTP 请求，就已经跨出 Kafka 事务边界，需要数据库幂等、Outbox、CDC 或业务补偿。",[15,7372,7373],{},"Kafka 事务也不要与 RocketMQ 的“事务消息\u002F半消息”机制混为一谈，它们解决问题的路径不同。",[10,7375,7377],{"id":7376},"八数据库与-kafka-如何保持一致","八、数据库与 Kafka 如何保持一致",[48,7379,7380],{"id":7380},"错误的直觉方案",[187,7382,7385],{"className":7383,"code":7384,"language":192,"meta":193},[190],"更新数据库\n→ 发送 Kafka\n",[195,7386,7384],{"__ignoreMap":193},[15,7388,7389],{},"数据库提交后发送失败，会丢事件。反过来先发送再提交，数据库失败时又会出现无效事件。",[48,7391,7393],{"id":7392},"transactional-outbox","Transactional Outbox",[15,7395,7396],{},"在同一个数据库事务中写业务表和 Outbox 表：",[187,7398,7401],{"className":7399,"code":7400,"language":192,"meta":193},[190],"数据库事务：\n  更新业务数据\n  插入 Outbox 事件\n  ↓\n提交\n  ↓\n后台任务或 CDC 发布到 Kafka\n",[195,7402,7400],{"__ignoreMap":193},[15,7404,7405],{},"发布过程可能重复，因此消费者仍要幂等。Outbox 提供的是可恢复的最终一致性，不是让数据库和 Kafka 共享一个全局事务。",[48,7407,7409],{"id":7408},"cdc","CDC",[15,7411,7412],{},"通过 binlog 捕获数据库已提交变更并发布事件，可降低业务代码双写复杂度。需要治理表结构变化、事件语义、重放和重复投递。",[10,7414,7416],{"id":7415},"九顺序消息","九、顺序消息",[15,7418,7419],{},"Kafka 只保证单分区内有序。要保证同一订单、账户或账单的事件顺序，应使用稳定 Key 让它们进入同一分区：",[187,7421,7424],{"className":7422,"code":7423,"language":192,"meta":193},[190],"key = accountId\n",[195,7425,7423],{"__ignoreMap":193},[15,7427,7428],{},"全局有序通常意味着只使用一个分区，会牺牲并行度。多数业务真正需要的是聚合维度有序，而不是全局有序。",[15,7430,7431],{},"即使分区有序，也要处理重试、异步线程池和下游并行处理带来的业务乱序，可结合版本号和状态机拒绝旧事件。",[10,7433,7435],{"id":7434},"十重试与死信","十、重试与死信",[15,7437,7438],{},"Kafka Broker 原生保存日志，并不会自动替业务创建“重试队列”和“死信队列”。工程中通常创建普通 Topic：",[187,7440,7443],{"className":7441,"code":7442,"language":192,"meta":193},[190],"business-topic\nbusiness-retry-1m\nbusiness-retry-10m\nbusiness-dlq\n",[195,7444,7442],{"__ignoreMap":193},[15,7446,7447],{},"由应用或框架负责：",[19,7449,7450,7453,7456,7459],{},[22,7451,7452],{},"记录重试次数和下一次执行时间；",[22,7454,7455],{},"区分可重试异常与永久异常；",[22,7457,7458],{},"超过阈值进入 DLQ；",[22,7460,7461],{},"提供人工查看、修复和重放能力。",[15,7463,7464],{},"不要无限立即重试，否则会阻塞正常消息并放大下游故障。",[10,7466,7468],{"id":7467},"十一消息积压怎么处理","十一、消息积压怎么处理",[15,7470,7471],{},"先判断瓶颈：",[187,7473,7476],{"className":7474,"code":7475,"language":192,"meta":193},[190],"生产突然增加？\n消费者实例不足？\n分区数不足？\n单条业务处理太慢？\n数据库、缓存或远程接口变慢？\n频繁 Rebalance？\n毒消息反复失败？\n",[195,7477,7475],{"__ignoreMap":193},[48,7479,7480],{"id":7480},"治理步骤",[292,7482,7483,7486,7489,7492,7495,7498,7501],{},[22,7484,7485],{},"看 Consumer Lag、消费速率、分区分布和失败率；",[22,7487,7488],{},"优化单条处理，使用批量查询、批量写和连接池；",[22,7490,7491],{},"在原消费者组内增加消费者，直到接近分区数；",[22,7493,7494],{},"并行度仍不足时评估扩分区，并检查顺序和 Key 分布；",[22,7496,7497],{},"把慢外部依赖隔离、限流或异步化；",[22,7499,7500],{},"将毒消息转入重试\u002FDLQ，避免卡住主链路；",[22,7502,7503],{},"恢复后控制追赶速度，避免把数据库二次打垮。",[15,7505,7506],{},"不能简单创建一个临时消费者组“帮原组消费”，因为不同组拥有独立 offset；临时组读完不会推进原组的消费进度，还可能重复执行业务。真正协助应加入同一个消费者组，或设计受控的数据转移方案。",[10,7508,7510],{"id":7509},"十二rebalance","十二、Rebalance",[15,7512,7513],{},"消费者实例加入、退出，订阅变化或分区变化都可能触发 Rebalance。期间分区重新分配，可能出现短暂停顿。",[15,7515,7516],{},"治理方向包括：",[19,7518,7519,7522,7525,7528],{},[22,7520,7521],{},"避免消费线程被长时间阻塞导致心跳异常；",[22,7523,7524],{},"合理配置 poll 批次与最大处理间隔；",[22,7526,7527],{},"使用静态成员或增量协作式分配减少抖动；",[22,7529,7530],{},"把不可控慢调用移出核心 poll 循环，但要正确管理 offset 和背压。",[10,7532,7534],{"id":7533},"十三速记结论","十三、速记结论",[489,7536,7537],{},[15,7538,7539],{},"生产可靠看 ACK、ISR 和幂等生产者；消费可靠看处理与 offset 的顺序；业务不重复靠幂等键和状态机；Kafka exactly-once 主要覆盖 Kafka 内部链路，跨数据库仍要 Outbox\u002FCDC 和幂等；积压先找瓶颈，扩容必须尊重分区并行度和消费者组语义。",[10,7541,496],{"id":496},[19,7543,7544,7551,7558,7565],{},[22,7545,7546],{},[502,7547,7550],{"href":7548,"rel":7549},"https:\u002F\u002Fkafka.apache.org\u002Fdocumentation\u002F#design",[506],"Apache Kafka 设计文档",[22,7552,7553],{},[502,7554,7557],{"href":7555,"rel":7556},"https:\u002F\u002Fkafka.apache.org\u002Fdocumentation\u002F#producerconfigs",[506],"Apache Kafka 生产者配置",[22,7559,7560],{},[502,7561,7564],{"href":7562,"rel":7563},"https:\u002F\u002Fkafka.apache.org\u002Fdocumentation\u002F#consumerconfigs",[506],"Apache Kafka 消费者配置",[22,7566,7567],{},[502,7568,7571],{"href":7569,"rel":7570},"https:\u002F\u002Fkafka.apache.org\u002F42\u002Fsecurity\u002Fauthorization-and-acls\u002F",[506],"Apache Kafka 协议与安全资源",[1923,7573,7574],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":193,"searchDepth":530,"depth":530,"links":7576},[7577,7578,7579,7583,7584,7587,7592,7593,7598,7599,7600,7603,7604,7605],{"id":7036,"depth":530,"text":7037},{"id":7060,"depth":530,"text":7061},{"id":7091,"depth":530,"text":7092,"children":7580},[7581,7582],{"id":7095,"depth":536,"text":7096},{"id":7139,"depth":536,"text":7139},{"id":7148,"depth":530,"text":7149},{"id":7175,"depth":530,"text":7176,"children":7585},[7586],{"id":7194,"depth":536,"text":7195},{"id":7294,"depth":530,"text":7295,"children":7588},[7589,7590,7591],{"id":7301,"depth":536,"text":7301},{"id":7309,"depth":536,"text":7309},{"id":7343,"depth":536,"text":7344},{"id":7350,"depth":530,"text":7351},{"id":7376,"depth":530,"text":7377,"children":7594},[7595,7596,7597],{"id":7380,"depth":536,"text":7380},{"id":7392,"depth":536,"text":7393},{"id":7408,"depth":536,"text":7409},{"id":7415,"depth":530,"text":7416},{"id":7434,"depth":530,"text":7435},{"id":7467,"depth":530,"text":7468,"children":7601},[7602],{"id":7480,"depth":536,"text":7480},{"id":7509,"depth":530,"text":7510},{"id":7533,"depth":530,"text":7534},{"id":496,"depth":530,"text":496},"wiki:java:kafka-mq-reliability","从分区副本、生产消费可靠性到业务幂等、事务边界和消息积压，系统理解 Kafka。",{},62,"\u002Fjava\u002Fkafka-mq-reliability",{"title":1115,"description":7607},"java\u002Fkafka-mq-reliability","9foTMzONcrHDfwkzn1pxXBWLU_sBNrIuD3wAyDPPmM0",1784181343406]