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