[{"data":1,"prerenderedAt":544},["ShallowReactive",2],{"\u002Fjava-interview\u002Fconcurrency":3,"java-interview-search:\u002Fjava-interview\u002Fconcurrency":499},{"id":4,"title":5,"body":6,"category":485,"cover":486,"date":487,"description":488,"extension":489,"group":490,"meta":491,"navigation":492,"order":493,"path":494,"seo":495,"sitemap":496,"stem":497,"__hash__":498},"javaInterview\u002Fjava-interview\u002Fconcurrency.md","Java 并发：JMM、锁与线程池",{"type":7,"value":8,"toc":467},"minimark",[9,18,88,93,118,121,144,151,164,167,170,177,184,190,196,200,211,214,225,236,250,263,275,279,285,288,291,302,310,323,333,352,356,371,381,384,387,418,428,431],[10,11,12,13,17],"p",{},"并发题最忌讳只背名词。建议先建立一条主线：多个线程访问共享状态时，需要同时处理可见性、原子性和有序性，",[14,15,16],"code",{},"volatile","、锁、CAS、线程池都是围绕这三件事展开的工具。",[19,20,21,37],"table",{},[22,23,24],"thead",{},[25,26,27,31,34],"tr",{},[28,29,30],"th",{},"工具",[28,32,33],{},"解决什么",[28,35,36],{},"不解决什么",[38,39,40,53,66,77],"tbody",{},[25,41,42,47,50],{},[43,44,45],"td",{},[14,46,16],{},[43,48,49],{},"可见性、禁止重排",[43,51,52],{},"复合操作原子性",[25,54,55,60,63],{},[43,56,57],{},[14,58,59],{},"synchronized",[43,61,62],{},"互斥、可见性、有序性",[43,64,65],{},"跨进程、超时中断",[25,67,68,71,74],{},[43,69,70],{},"CAS",[43,72,73],{},"单个变量的原子更新",[43,75,76],{},"多变量事务、ABA",[25,78,79,82,85],{},[43,80,81],{},"线程池",[43,83,84],{},"线程复用、限流、治理",[43,86,87],{},"任务内部的线程安全",[89,90,92],"h2",{"id":91},"_1-创建线程有哪几种方式为什么推荐线程池","1. 创建线程有哪几种方式？为什么推荐线程池？",[10,94,95,96,99,100,103,104,106,107,106,110,113,114,117],{},"本质上只有一种：构造 ",[14,97,98],{},"Thread"," 并调用 ",[14,101,102],{},"start()","。所谓多种方式是任务的定义形式不同：继承 ",[14,105,98],{},"、实现 ",[14,108,109],{},"Runnable",[14,111,112],{},"Callable"," 配合 ",[14,115,116],{},"FutureTask","、提交到线程池，JDK 21 还可以用虚拟线程执行器。",[10,119,120],{},"推荐线程池的原因：线程创建和销毁有系统调用与栈分配开销；无限制创建会耗尽内存和调度资源；池化后可以统一控制并发度、队列、拒绝策略、命名和监控。",[10,122,123,124,127,128,131,132,135,136,139,140,143],{},"直接用 ",[14,125,126],{},"new Thread"," 的代码通常缺少统一的异常处理和关闭逻辑。",[14,129,130],{},"execute"," 抛出的异常会交给线程的未捕获异常处理器，",[14,133,134],{},"submit"," 则会把它封装进 ",[14,137,138],{},"Future","，不调用 ",[14,141,142],{},"get"," 就永远看不到——这是线上「任务静默失败」的常见原因。",[89,145,147,148,150],{"id":146},"_2-synchronized-的实现原理是什么锁升级是怎么回事","2. ",[14,149,59],{}," 的实现原理是什么？锁升级是怎么回事？",[10,152,153,155,156,159,160,163],{},[14,154,59],{}," 编译成 ",[14,157,158],{},"monitorenter"," \u002F ",[14,161,162],{},"monitorexit"," 字节码，修饰方法时通过方法访问标志隐式加锁。锁信息记录在对象头的 Mark Word 中。",[10,165,166],{},"传统讲解分为偏向锁、轻量级锁、重量级锁：无竞争时偏向某个线程；出现竞争时用 CAS 尝试把 Mark Word 指向栈上的锁记录；竞争加剧则膨胀为操作系统互斥量，未抢到的线程进入等待队列。",[10,168,169],{},"需要补充版本边界：偏向锁从 JDK 15 起默认关闭并被标记废弃，后续版本已移除，现代 JDK 不应再把它当作必然存在的优化。锁升级在常规理解中不可逆，但具体实现有安全点等细节，面试不必展开。",[89,171,173,174,176],{"id":172},"_3-volatile-能保证什么不能保证什么","3. ",[14,175,16],{}," 能保证什么、不能保证什么？",[10,178,179,180,183],{},"它保证可见性：写操作会刷新到主内存，读操作会从主内存重新读取；同时禁止特定重排序，建立 happens-before 关系。它不保证复合操作的原子性，",[14,181,182],{},"count++"," 依然是读、加、写三步。",[10,185,186,187,189],{},"典型正确用法：状态标志位、一次性安全发布（双重检查锁单例的实例字段）、作为 CAS 的底层语义基础。典型错误用法：用 ",[14,188,16],{}," 修饰计数器，或用它保护「先检查再修改」的逻辑。",[10,191,192,193,195],{},"双重检查锁里必须给实例字段加 ",[14,194,16],{},"，否则可能先写入引用、后完成构造，其他线程会看到一个「半初始化」对象。",[89,197,199],{"id":198},"_4-cas-是什么aba-问题怎么解决","4. CAS 是什么？ABA 问题怎么解决？",[10,201,202,203,206,207,210],{},"CAS 包含三个值：内存位置、期望值、新值，仅当当前值等于期望值时才更新，由 CPU 指令保证原子性。Java 通过 ",[14,204,205],{},"Unsafe"," 的本地方法暴露，",[14,208,209],{},"AtomicInteger"," 等类封装了它。",[10,212,213],{},"它避免了加锁的阻塞和上下文切换，但有几个问题：自旋失败会空耗 CPU；只能保证单个变量的原子性；存在 ABA——值从 A 改成 B 又改回 A，CAS 无法察觉中间变化。",[10,215,216,217,220,221,224],{},"ABA 的解决思路是加版本号，",[14,218,219],{},"AtomicStampedReference"," 就是「值 + 版本」；如果对象引用变化本身无害，也可以忽略。高并发计数场景可以用 ",[14,222,223],{},"LongAdder","，它把热点分散到多个 Cell，代价是求和结果不是某一瞬间的精确快照。",[89,226,228,229,232,233,235],{"id":227},"_5-aqs-的原理是什么reentrantlock-和-synchronized-怎么选","5. AQS 的原理是什么？",[14,230,231],{},"ReentrantLock"," 和 ",[14,234,59],{}," 怎么选？",[10,237,238,239,242,243,159,246,249],{},"AQS 用一个 ",[14,240,241],{},"volatile int state"," 表示同步状态，加一个 FIFO 的双向等待队列。获取失败的线程被包装成节点入队并挂起，释放时唤醒后继节点；子类只需实现 ",[14,244,245],{},"tryAcquire",[14,247,248],{},"tryRelease"," 等模板方法。",[10,251,252,254,255,258,259,262],{},[14,253,231],{}," 用 state 记录重入次数，",[14,256,257],{},"Semaphore"," 用它表示许可数，",[14,260,261],{},"CountDownLatch"," 用它表示剩余计数。公平锁在获取时先检查队列中是否有前驱，非公平锁允许新线程直接抢，吞吐通常更高。",[10,264,265,266,268,269,271,272,274],{},"选型上：需要可中断获取、超时、公平性或多个条件队列时用 ",[14,267,231],{},"；否则优先 ",[14,270,59],{},"，它由 JVM 优化、写法简单、不会忘记释放。虚拟线程场景下还要注意，JDK 21 的 ",[14,273,59],{}," 会钉住载体线程。",[89,276,278],{"id":277},"_6-线程池的核心参数和执行流程线程数怎么设","6. 线程池的核心参数和执行流程？线程数怎么设？",[10,280,281,284],{},[14,282,283],{},"ThreadPoolExecutor"," 有七个参数：核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略。",[10,286,287],{},"提交任务时：核心线程未满就新建线程；核心满了进队列；队列满了且未达最大线程数就继续建线程；再满则执行拒绝策略。所以无界队列会让最大线程数永远不生效。",[10,289,290],{},"线程数没有万能公式。CPU 密集任务通常取核数或核数加一；IO 密集可用「核数 ×（1 + 等待时间 \u002F 计算时间）」估算，再结合实际压测。比公式更重要的是给队列设上界、给线程命名、监控活跃数与队列长度。",[10,292,293,294,297,298,301],{},"拒绝策略默认是 ",[14,295,296],{},"AbortPolicy","，直接抛异常。 ",[14,299,300],{},"CallerRunsPolicy"," 让提交线程执行，能形成天然反压，但会阻塞上游，需要确认业务能接受。",[89,303,305,306,309],{"id":304},"_7-threadlocal-的原理和内存泄漏原因","7. ",[14,307,308],{},"ThreadLocal"," 的原理和内存泄漏原因？",[10,311,312,313,315,316,319,320,322],{},"每个 ",[14,314,98],{}," 内部有一个 ",[14,317,318],{},"ThreadLocalMap","，key 是 ",[14,321,308],{}," 的弱引用，value 是强引用。线程复用（尤其是线程池）时，map 会跟着线程长期存活。",[10,324,325,326,328,329,332],{},"泄漏的根源不是 key 的弱引用本身，而是「key 被回收后，value 仍被 Entry 强引用，而 Entry 又被线程持有」。虽然 ",[14,327,142],{},"、",[14,330,331],{},"set"," 会顺带清理部分过期条目，但不保证及时。",[10,334,335,336,339,340,343,344,347,348,351],{},"规范做法是在 ",[14,337,338],{},"finally"," 中调用 ",[14,341,342],{},"remove()","。",[14,345,346],{},"InheritableThreadLocal"," 只在创建线程时拷贝，线程池复用会失效；跨线程池传递上下文需要 ",[14,349,350],{},"TransmittableThreadLocal"," 这类方案。虚拟线程数量极大，更要注意 ThreadLocal 的内存占用。",[89,353,355],{"id":354},"_8-如何排查死锁虚拟线程又解决了什么问题","8. 如何排查死锁？虚拟线程又解决了什么问题？",[10,357,358,359,362,363,366,367,370],{},"死锁需要同时满足互斥、持有并等待、不可抢占、循环等待四个条件。排查时先用 ",[14,360,361],{},"jcmd \u003Cpid> Thread.print -l"," 或 ",[14,364,365],{},"jstack"," 抓线程栈，JDK 会直接输出 ",[14,368,369],{},"Found one Java-level deadlock"," 以及相互等待的锁和代码行。",[10,372,373,374,377,378,343],{},"预防手段：统一加锁顺序、用 ",[14,375,376],{},"tryLock"," 带超时、缩小临界区、避免在持锁时调用外部服务。数据库死锁则是另一套机制，要看 ",[14,379,380],{},"SHOW ENGINE INNODB STATUS",[10,382,383],{},"虚拟线程解决的是「大量阻塞任务导致平台线程耗尽」的问题：阻塞时自动让出载体线程，调度成本低，适合高并发 IO。它不提升单核计算能力，也不能突破下游连接池、数据库连接数的瓶颈；用信号量限制并发往往比池化虚拟线程更合适。",[89,385,386],{"id":386},"易错点与自测",[388,389,390,403,411],"ul",{},[391,392,393,395,396,398,399,402],"li",{},[14,394,16],{}," 不等于原子，",[14,397,59],{}," 不等于公平，",[14,400,401],{},"ConcurrentHashMap"," 不等于复合操作原子。",[391,404,405,408,409,343],{},[14,406,407],{},"Executors"," 的快捷工厂方法隐藏了无界队列和无上限线程数，生产环境应显式构造 ",[14,410,283],{},[391,412,413,414,417],{},"ThreadLocal 必须 ",[14,415,416],{},"remove","，线程池与虚拟线程会放大忘记清理的后果。",[10,419,420,421,423,424,427],{},"自测：① 写出双重检查锁单例并解释 ",[14,422,16],{}," 的作用；② 说明线程池在「核心满、队列满、未到最大线程数」时的行为；③ 用 ",[14,425,426],{},"jcmd"," 命令列出排查死锁的操作。",[89,429,430],{"id":430},"参考资料",[388,432,433,443,451,459],{},[391,434,435,442],{},[436,437,441],"a",{"href":438,"rel":439},"https:\u002F\u002Fdocs.oracle.com\u002Fjavase\u002Fspecs\u002Fjls\u002Fse21\u002Fhtml\u002Fjls-17.html",[440],"nofollow","JLS SE 21 第 17 章：线程与锁"," — happens-before 与内存模型的规范定义",[391,444,445,450],{},[436,446,449],{"href":447,"rel":448},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F21\u002Fdocs\u002Fapi\u002Fjava.base\u002Fjava\u002Futil\u002Fconcurrent\u002FThreadPoolExecutor.html",[440],"ThreadPoolExecutor API"," — 参数语义、执行流程与拒绝策略",[391,452,453,458],{},[436,454,457],{"href":455,"rel":456},"https:\u002F\u002Fopenjdk.org\u002Fjeps\u002F374",[440],"JEP 374：Deprecate and Disable Biased Locking"," — 偏向锁的版本边界",[391,460,461,466],{},[436,462,465],{"href":463,"rel":464},"https:\u002F\u002Fopenjdk.org\u002Fjeps\u002F444",[440],"JEP 444：Virtual Threads"," — 虚拟线程的调度模型与限制",{"title":468,"searchDepth":469,"depth":469,"links":470},"",2,[471,472,474,476,477,479,480,482,483,484],{"id":91,"depth":469,"text":92},{"id":146,"depth":469,"text":473},"2. synchronized 的实现原理是什么？锁升级是怎么回事？",{"id":172,"depth":469,"text":475},"3. volatile 能保证什么、不能保证什么？",{"id":198,"depth":469,"text":199},{"id":227,"depth":469,"text":478},"5. AQS 的原理是什么？ReentrantLock 和 synchronized 怎么选？",{"id":277,"depth":469,"text":278},{"id":304,"depth":469,"text":481},"7. ThreadLocal 的原理和内存泄漏原因？",{"id":354,"depth":469,"text":355},{"id":386,"depth":469,"text":386},{"id":430,"depth":469,"text":430},"java-interview",null,"2026-10-24","围绕可见性、原子性、有序性梳理 volatile、CAS、AQS、线程池和 ThreadLocal，补上虚拟线程的适用边界","md","Java 核心",{},true,"40","\u002Fjava-interview\u002Fconcurrency",{"title":5,"description":488},{"loc":494},"java-interview\u002Fconcurrency","YRV5UW4RO-4T4W3r6nZwRHvUi2h4iXBgYFvE3TUoWyI",[500,504,508,512,516,520,524,528,532,536,540],{"id":494,"title":5,"titles":501,"content":502,"level":503},[],"围绕可见性、原子性、有序性梳理 volatile、CAS、AQS、线程池和 ThreadLocal，补上虚拟线程的适用边界 并发题最忌讳只背名词。建议先建立一条主线：多个线程访问共享状态时，需要同时处理可见性、原子性和有序性，volatile、锁、CAS、线程池都是围绕这三件事展开的工具。 工具 解决什么 不解决什么 volatile 可见性、禁止重排 复合操作原子性 synchronized 互斥、可见性、有序性 跨进程、超时中断 CAS 单个变量的原子更新 多变量事务、ABA 线程池 线程复用、限流、治理 任务内部的线程安全",1,{"id":505,"title":92,"titles":506,"content":507,"level":469},"\u002Fjava-interview\u002Fconcurrency#_1-创建线程有哪几种方式为什么推荐线程池",[5],"本质上只有一种：构造 Thread 并调用 start()。所谓多种方式是任务的定义形式不同：继承 Thread、实现 Runnable、实现 Callable 配合 FutureTask、提交到线程池，JDK 21 还可以用虚拟线程执行器。 推荐线程池的原因：线程创建和销毁有系统调用与栈分配开销；无限制创建会耗尽内存和调度资源；池化后可以统一控制并发度、队列、拒绝策略、命名和监控。 直接用 new Thread 的代码通常缺少统一的异常处理和关闭逻辑。execute 抛出的异常会交给线程的未捕获异常处理器，submit 则会把它封装进 Future，不调用 get 就永远看不到——这是线上「任务静默失败」的常见原因。",{"id":509,"title":473,"titles":510,"content":511,"level":469},"\u002Fjava-interview\u002Fconcurrency#_2-synchronized-的实现原理是什么锁升级是怎么回事",[5],"synchronized 编译成 monitorenter \u002F monitorexit 字节码，修饰方法时通过方法访问标志隐式加锁。锁信息记录在对象头的 Mark Word 中。 传统讲解分为偏向锁、轻量级锁、重量级锁：无竞争时偏向某个线程；出现竞争时用 CAS 尝试把 Mark Word 指向栈上的锁记录；竞争加剧则膨胀为操作系统互斥量，未抢到的线程进入等待队列。 需要补充版本边界：偏向锁从 JDK 15 起默认关闭并被标记废弃，后续版本已移除，现代 JDK 不应再把它当作必然存在的优化。锁升级在常规理解中不可逆，但具体实现有安全点等细节，面试不必展开。",{"id":513,"title":475,"titles":514,"content":515,"level":469},"\u002Fjava-interview\u002Fconcurrency#_3-volatile-能保证什么不能保证什么",[5],"它保证可见性：写操作会刷新到主内存，读操作会从主内存重新读取；同时禁止特定重排序，建立 happens-before 关系。它不保证复合操作的原子性，count++ 依然是读、加、写三步。 典型正确用法：状态标志位、一次性安全发布（双重检查锁单例的实例字段）、作为 CAS 的底层语义基础。典型错误用法：用 volatile 修饰计数器，或用它保护「先检查再修改」的逻辑。 双重检查锁里必须给实例字段加 volatile，否则可能先写入引用、后完成构造，其他线程会看到一个「半初始化」对象。",{"id":517,"title":199,"titles":518,"content":519,"level":469},"\u002Fjava-interview\u002Fconcurrency#_4-cas-是什么aba-问题怎么解决",[5],"CAS 包含三个值：内存位置、期望值、新值，仅当当前值等于期望值时才更新，由 CPU 指令保证原子性。Java 通过 Unsafe 的本地方法暴露，AtomicInteger 等类封装了它。 它避免了加锁的阻塞和上下文切换，但有几个问题：自旋失败会空耗 CPU；只能保证单个变量的原子性；存在 ABA——值从 A 改成 B 又改回 A，CAS 无法察觉中间变化。 ABA 的解决思路是加版本号，AtomicStampedReference 就是「值 + 版本」；如果对象引用变化本身无害，也可以忽略。高并发计数场景可以用 LongAdder，它把热点分散到多个 Cell，代价是求和结果不是某一瞬间的精确快照。",{"id":521,"title":478,"titles":522,"content":523,"level":469},"\u002Fjava-interview\u002Fconcurrency#_5-aqs-的原理是什么reentrantlock-和-synchronized-怎么选",[5],"AQS 用一个 volatile int state 表示同步状态，加一个 FIFO 的双向等待队列。获取失败的线程被包装成节点入队并挂起，释放时唤醒后继节点；子类只需实现 tryAcquire \u002F tryRelease 等模板方法。 ReentrantLock 用 state 记录重入次数，Semaphore 用它表示许可数，CountDownLatch 用它表示剩余计数。公平锁在获取时先检查队列中是否有前驱，非公平锁允许新线程直接抢，吞吐通常更高。 选型上：需要可中断获取、超时、公平性或多个条件队列时用 ReentrantLock；否则优先 synchronized，它由 JVM 优化、写法简单、不会忘记释放。虚拟线程场景下还要注意，JDK 21 的 synchronized 会钉住载体线程。",{"id":525,"title":278,"titles":526,"content":527,"level":469},"\u002Fjava-interview\u002Fconcurrency#_6-线程池的核心参数和执行流程线程数怎么设",[5],"ThreadPoolExecutor 有七个参数：核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略。 提交任务时：核心线程未满就新建线程；核心满了进队列；队列满了且未达最大线程数就继续建线程；再满则执行拒绝策略。所以无界队列会让最大线程数永远不生效。 线程数没有万能公式。CPU 密集任务通常取核数或核数加一；IO 密集可用「核数 ×（1 + 等待时间 \u002F 计算时间）」估算，再结合实际压测。比公式更重要的是给队列设上界、给线程命名、监控活跃数与队列长度。 拒绝策略默认是 AbortPolicy，直接抛异常。 CallerRunsPolicy 让提交线程执行，能形成天然反压，但会阻塞上游，需要确认业务能接受。",{"id":529,"title":481,"titles":530,"content":531,"level":469},"\u002Fjava-interview\u002Fconcurrency#_7-threadlocal-的原理和内存泄漏原因",[5],"每个 Thread 内部有一个 ThreadLocalMap，key 是 ThreadLocal 的弱引用，value 是强引用。线程复用（尤其是线程池）时，map 会跟着线程长期存活。 泄漏的根源不是 key 的弱引用本身，而是「key 被回收后，value 仍被 Entry 强引用，而 Entry 又被线程持有」。虽然 get、set 会顺带清理部分过期条目，但不保证及时。 规范做法是在 finally 中调用 remove()。InheritableThreadLocal 只在创建线程时拷贝，线程池复用会失效；跨线程池传递上下文需要 TransmittableThreadLocal 这类方案。虚拟线程数量极大，更要注意 ThreadLocal 的内存占用。",{"id":533,"title":355,"titles":534,"content":535,"level":469},"\u002Fjava-interview\u002Fconcurrency#_8-如何排查死锁虚拟线程又解决了什么问题",[5],"死锁需要同时满足互斥、持有并等待、不可抢占、循环等待四个条件。排查时先用 jcmd \u003Cpid> Thread.print -l 或 jstack 抓线程栈，JDK 会直接输出 Found one Java-level deadlock 以及相互等待的锁和代码行。 预防手段：统一加锁顺序、用 tryLock 带超时、缩小临界区、避免在持锁时调用外部服务。数据库死锁则是另一套机制，要看 SHOW ENGINE INNODB STATUS。 虚拟线程解决的是「大量阻塞任务导致平台线程耗尽」的问题：阻塞时自动让出载体线程，调度成本低，适合高并发 IO。它不提升单核计算能力，也不能突破下游连接池、数据库连接数的瓶颈；用信号量限制并发往往比池化虚拟线程更合适。",{"id":537,"title":386,"titles":538,"content":539,"level":469},"\u002Fjava-interview\u002Fconcurrency#易错点与自测",[5],"volatile 不等于原子，synchronized 不等于公平，ConcurrentHashMap 不等于复合操作原子。 Executors 的快捷工厂方法隐藏了无界队列和无上限线程数，生产环境应显式构造 ThreadPoolExecutor。 ThreadLocal 必须 remove，线程池与虚拟线程会放大忘记清理的后果。 自测：① 写出双重检查锁单例并解释 volatile 的作用；② 说明线程池在「核心满、队列满、未到最大线程数」时的行为；③ 用 jcmd 命令列出排查死锁的操作。",{"id":541,"title":430,"titles":542,"content":543,"level":469},"\u002Fjava-interview\u002Fconcurrency#参考资料",[5],"JLS SE 21 第 17 章：线程与锁 — happens-before 与内存模型的规范定义 ThreadPoolExecutor API — 参数语义、执行流程与拒绝策略 JEP 374：Deprecate and Disable Biased Locking — 偏向锁的版本边界 JEP 444：Virtual Threads — 虚拟线程的调度模型与限制",1793263559673]