volatile能保证原子性吗?
这是淘汰率最高的开场题。很多人脱口而出“能”,直接出局。volatile只保证可见性和有序性,不保证原子性。i++这种操作包含读取、计算、写入三步,多线程下依然会丢失更新。正确回答要补一句:需要原子性得靠synchronized、ReentrantLock或AtomicInteger这类原子类。如果再能点出volatile的底层实现——通过内存屏障禁止指令重排、强制刷新主内存——面试官会高看一眼。
synchronized锁升级的过程
能说出“偏向锁、轻量级锁、重量级锁”只是及格线。真正拉开差距的是解释为什么要有升级。偏向锁针对的是同一个线程反复进入同步块的场景,把线程ID记在对象头里,连CAS都省了。一旦有另一个线程来竞争,升级为轻量级锁,用CAS自旋尝试获取。自旋失败到一定次数,膨胀为重量级锁,线程挂起进入等待队列。锁升级的本质是用CPU空转换线程切换开销,在竞争程度和性能之间找平衡。能讲清楚这个权衡,说明你理解JVM的设计哲学,而不是死记硬背。
线程池的核心参数和执行流程
这个问题几乎每场必问。七个核心参数要能一口气说清楚:核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略。关键在描述执行流程:任务来了先看核心线程是否满,没满就创建;满了就丢进队列;队列满了才创建非核心线程;非核心也满了就触发拒绝策略。很多人把队列和最大线程数的顺序记反了,这是致命伤。如果再能说出四种拒绝策略分别是什么、默认是AbortPolicy、以及为什么生产环境不建议用Executors快捷方法创建线程池,这个问题就算答到位了。
ThreadLocal为什么会内存泄漏
ThreadLocal本身不泄漏,泄漏的是ThreadLocalMap里的Entry。Entry的key是弱引用,value是强引用。当ThreadLocal对象被回收后,key变成null,但value还被Entry强引用着,而Entry又被Thread对象持有。如果线程一直存活(比如线程池里的核心线程),这个value就永远回收不掉。解决方案是在finally里手动调用remove()。能把这个引用链讲清楚,说明你看过源码,不是只会用API。
CAS的ABA问题怎么解决
CAS是乐观锁的基石,但存在ABA问题:值从A改成B又改回A,CAS检查发现还是A就认为没变过。解决方法是加版本号,Java里用AtomicStampedReference,每次修改同时更新版本戳。再深入一点可以提:ABA问题在大多数业务场景下影响不大,但在链表结构里可能导致节点丢失,所以并发容器里用CAS时要格外小心。能主动说出适用场景和边界,是高级工程师和普通开发的分水岭。
这五个问题有一个共同点:它们都不考记忆力,考的是你对“为什么这样设计”的理解。背答案只能应付一面,真正决定能否通过二面和定级的,是你能否在追问下依然逻辑自洽。多线程的每一行代码背后都是权衡,把权衡讲明白,比把概念背熟练重要得多。