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