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