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