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