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