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