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