
上周帮朋友复盘一场Java后端面试他工作了五年自认为并发编程还算熟悉结果被五道题问得哑口无言。面试官事后说这五道题刷掉了八成候选人——不是因为他们没背过八股文而是因为大多数人只记住了结论却说不清背后的“为什么”。我把它整理出来附上解析和常见误区希望能帮你绕过这些坑。第一题synchronized锁升级到底锁了什么“请描述synchronized的锁升级过程。”很多人能背出“偏向锁→轻量级锁→重量级锁”但面试官紧接着问“偏向锁撤销时为什么要等到全局安全点”这一问就筛掉了一半人。关键不在于记住流程而在于理解锁升级的本质是JVM对竞争程度的自适应优化。偏向锁假设只有一个线程访问轻量级锁假设竞争不激烈用CAS自旋重量级锁才真正挂起线程。撤销偏向锁需要暂停所有线程STW因为要遍历栈帧确认没有线程持有该锁——这个成本很高所以JVM设置了批量重偏向和批量撤销来降低开销。误区很多人以为锁升级是“性能变差”的过程实际上它是JVM在“无竞争→低竞争→高竞争”场景下不断寻找最优解的过程。第二题volatile真的能保证原子性吗“volatile关键字的作用是什么”标准答案通常是“保证可见性和有序性不保证原子性”。但面试官会追问“那为什么i用volatile修饰后仍然线程不安全从JMM角度解释。”这题考的是对Java内存模型的理解。volatile保证了写操作立即刷新到主内存、读操作从主内存读取但i是“读-改-写”三步操作中间任何一步都可能被其他线程插入。可见性解决的是“一个线程改了另一个线程能看见”但解决不了“两个线程同时改”的竞态条件。深入一层volatile通过内存屏障禁止指令重排序写操作前插入StoreStore屏障写操作后插入StoreLoad屏障。这些屏障的插入位置和开销才是面试官想听到的。第三题线程池参数怎么设别再说“核心数1”了“线程池的corePoolSize和maxPoolSize怎么设置”如果你回答“CPU密集型设为N1IO密集型设为2N”面试官可能会让你回去等通知。因为真实场景远比公式复杂。正确的回答思路是先分析任务类型再结合系统资源最后通过压测验证。CPU密集型任务确实适合N1但IO密集型要考虑IO等待时间与CPU计算时间的比例。更关键的是线程池不是孤立的——数据库连接池、下游服务限流、GC停顿都会影响实际吞吐量。加分回答我会先用Runtime.getRuntime().availableProcessors()获取核数然后根据等待时间/计算时间的比值估算但最终一定通过压测调整。同时考虑使用LinkedBlockingQueue还是SynchronousQueue以及拒绝策略是CallerRunsPolicy还是AbortPolicy。第四题AQS为什么用CLH队列变体“请解释AQS的原理。”背出“stateCLH队列”只能拿及格分。面试官真正想听的是为什么AQS选择CLH队列的变体而不是MCS队列CLH队列是自旋锁队列每个节点自旋前驱节点的状态。AQS将其改为“阻塞队列”——节点不再自旋而是通过LockSupport.park()阻塞前驱节点释放锁时unpark()唤醒后继。这个改动的原因是自旋在锁竞争激烈时浪费CPU而阻塞/唤醒虽然上下文切换开销大但在高竞争下更稳定。更深一层AQS的state用volatile修饰配合CAS操作实现了“无锁化”的入队和状态变更。这种设计让ReentrantLock、Semaphore、CountDownLatch等都能复用同一套骨架。第五题ThreadLocal为什么会内存泄漏“ThreadLocal的底层结构是什么为什么会导致内存泄漏”这题几乎成了面试必问但大多数人只答出“Entry的key是弱引用”。面试官会继续问“那value为什么是强引用这样设计合理吗”关键在于理解ThreadLocalMap的生命周期与Thread绑定。Entry的key是弱引用当ThreadLocal对象没有外部强引用时会被GC回收但value是强引用且Entry被Thread持有。如果线程长期存活比如线程池value就无法被回收造成泄漏。解决方案每次使用完ThreadLocal后调用remove()。但更值得思考的是为什么JDK不把value也设为弱引用因为如果value也是弱引用可能导致正在使用的值被意外回收这是设计上的权衡。总结面试官到底在考什么这五道题看似在考知识点实则在考三个层次的能力知其然能说出结论和流程知其所以然能解释设计原因和权衡知其所用能结合实际场景做技术决策80%的人止步于第一层而面试官想要的是第三层。下次准备面试时不妨多问自己一句“这个设计为什么是这样换成别的方案会怎样”——这比背一百道八股文都管用。