1. JMM基础概念与核心问题
Java内存模型(JMM)是理解多线程编程的基石,它定义了线程如何与内存交互的规范。在实际面试中,面试官常通过以下典型问题考察候选人的理解深度:
1.1 可见性问题本质
当多个线程访问共享变量时,一个线程的修改可能对其他线程不可见。这种现象的根本原因在于现代CPU的多级缓存架构:
- 每个CPU核心有自己的L1/L2缓存
- 写操作可能暂时停留在store buffer
- 缓存一致性协议(如MESI)的延迟生效
// 典型可见性问题示例 public class VisibilityIssue { private static boolean flag = true; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (flag) {} // 可能永远无法退出循环 System.out.println("Thread stopped"); }).start(); Thread.sleep(1000); flag = false; } }关键点:volatile通过内存屏障强制刷新缓存,解决可见性问题
1.2 指令重排序陷阱
编译器和处理器会进行指令优化重排,可能破坏程序语义:
// 双重检查锁中的重排序问题 class Singleton { private static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // 问题根源! } } } return instance; } }这个经典案例中,new Singleton()可能被重排序为:
- 分配内存空间
- 将引用指向内存(此时instance非null)
- 执行构造函数
其他线程可能拿到未初始化完成的对象。解决方案是给instance加上volatile修饰。
2. 原子性操作深度解析
2.1 竞态条件实战分析
i++这类复合操作的非原子性会导致计数不准:
public class Counter { private int count; public void increment() { count++; // 实际包含read-modify-write三步操作 } }解决方案对比:
synchronized:最直接但性能开销大AtomicInteger:CAS无锁实现,适合高并发LongAdder:分段计数,写多读少场景最优
2.2 CAS机制底层原理
Atomic类基于Compare-And-Swap实现,其核心是:
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } // HotSpot源码片段 UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt( JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) oop p = JNIHandles::resolve(obj); jint* addr = (jint*)index_oop_from_field_offset_long(p, offset); return Atomic::cmpxchg(x, addr, e) == e; UNSAFE_ENDABA问题解决方案:
- 版本号机制(AtomicStampedReference)
- 标记位控制
3. happens-before规则详解
3.1 八大规则应用场景
- 程序顺序规则:同一线程内的操作按代码顺序
- 锁规则:解锁先于后续加锁
- volatile规则:写先于后续读
- 传递性规则:A先于B,B先于C ⇒ A先于C
- 线程启动规则:start()先于线程内任何操作
- 线程终止规则:线程内操作先于终止检测
- 中断规则:interrupt()调用先于中断检测
- 对象终结规则:构造方法先于finalize()
3.2 内存屏障实现机制
JVM插入内存屏障保证顺序性:
| 屏障类型 | 作用 | 对应指令 |
|---|---|---|
| LoadLoad | 禁止读-读重排序 | LFENCE |
| StoreStore | 禁止写-写重排序 | SFENCE |
| LoadStore | 禁止读-写重排序 | 无直接对应 |
| StoreLoad | 禁止写-读重排序(全能屏障) | MFENCE |
x86架构下StoreLoad屏障通过lock addl $0,0(%rsp)实现。
4. 线程安全设计模式
4.1 不可变对象实践
// 标准不可变类实现 public final class ImmutablePoint { private final int x; private final int y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; } // 只有getter没有setter public int getX() { return x; } public int getY() { return y; } // 返回新对象而非修改现有对象 public ImmutablePoint move(int dx, int dy) { return new ImmutablePoint(x + dx, y + dy); } }4.2 线程封闭技术
- 栈封闭:局部变量天然线程安全
- ThreadLocal模式:
public class UserContext { private static final ThreadLocal<User> currentUser = new ThreadLocal<>(); public static void set(User user) { currentUser.set(user); } public static User get() { return currentUser.get(); } public static void remove() { currentUser.remove(); // 防止内存泄漏 } }注意事项:线程池中使用ThreadLocal必须及时remove()
5. 锁优化高级技巧
5.1 锁升级过程图解
graph LR A[无锁] -->|首次访问| B[偏向锁] B -->|竞争发生| C[轻量级锁] C -->|持续竞争| D[重量级锁] D -->|释放后| A实际JVM实现更为复杂,涉及:
- 偏向锁延迟(-XX:BiasedLockingStartupDelay)
- 批量重偏向(-XX:BiasedLockingBulkRebiasThreshold)
- 锁消除(-XX:+DoEscapeAnalysis)
5.2 自旋锁优化参数
-XX:+UseSpinning # 启用自旋(JDK6默认) -XX:PreBlockSpin=10 # 默认自旋次数 -XX:+UseAdaptiveSizePolicy # 自适应自旋现代JVM使用更智能的适应性自旋策略,根据历史成功率动态调整。
6. 并发容器选型指南
6.1 ConcurrentHashMap演进
JDK7与JDK8实现对比:
| 特性 | JDK7 Segment分段锁 | JDK8 CAS+synchronized |
|---|---|---|
| 并发度 | 构造时固定 | 动态扩容 |
| 锁粒度 | 段级别 | 桶级别 |
| 数据结构 | 数组+链表 | 数组+链表/红黑树 |
| 扩容方式 | 段内扩容 | 协助扩容 |
| 统计size | 多次尝试+锁 | CounterCell分片计数 |
6.2 BlockingQueue对比
| 队列类型 | 特性 | 适用场景 |
|---|---|---|
| ArrayBlockingQueue | 固定容量,公平锁可选 | 固定资源池 |
| LinkedBlockingQueue | 可选容量,双锁队列 | 任务调度系统 |
| PriorityBlockingQueue | 优先级排序 | 紧急任务处理 |
| DelayQueue | 时间调度 | 缓存过期/定时任务 |
| SynchronousQueue | 直接传递 | 线程池任务交接 |
7. 线程池实战调优
7.1 参数配置公式
// 最优线程数估算 int optimalThreadCount = Runtime.getRuntime().availableProcessors() * (1 + (waitTime / computeTime)); // 实际配置示例 ExecutorService pool = new ThreadPoolExecutor( 4, // corePoolSize 16, // maximumPoolSize 60, TimeUnit.SECONDS, // keepAliveTime new LinkedBlockingQueue<>(1000), // workQueue new CustomThreadFactory(), // threadFactory new ThreadPoolExecutor.CallerRunsPolicy() // rejectionPolicy );7.2 监控关键指标
ThreadPoolExecutor pool = (ThreadPoolExecutor) executor; System.out.println("活跃线程数: " + pool.getActiveCount()); System.out.println("已完成任务: " + pool.getCompletedTaskCount()); System.out.println("队列大小: " + pool.getQueue().size()); System.out.println("最大线程数: " + pool.getLargestPoolSize());推荐使用Micrometer接入Prometheus实现可视化监控。
8. 常见面试题精讲
8.1 volatile与synchronized区别
| 维度 | volatile | synchronized |
|---|---|---|
| 原子性 | 仅保证单次读/写原子性 | 保证代码块原子性 |
| 可见性 | 强制主内存读写 | 通过锁机制保证 |
| 有序性 | 禁止指令重排序 | 限制临界区内的重排序 |
| 阻塞 | 非阻塞 | 阻塞 |
| 适用场景 | 状态标志 | 复合操作 |
8.2 死锁排查实战
- jstack检测:
jstack <pid> | grep -A 10 deadlock- Arthas诊断:
thread -b # 直接定位死锁线程- 预防方案:
- 统一锁获取顺序
- 使用tryLock()带超时
- 静态代码分析工具检测
9. JMM相关参数调优
9.1 内存屏障控制
-XX:+UseMemBarrier # 启用内存屏障(默认开启) -XX:MemBarrierCost=50 # 屏障开销估值(纳秒)9.2 字段重排限制
-XX:+CompactFields # 启用字段重排序(默认true) -XX:FieldAllocationStyle=0 # 0=按类型分组 1=按声明顺序10. 最新并发特性展望
10.1 虚拟线程(Loom项目)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i -> { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); }10.2 结构化并发
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Future<String> user = scope.fork(() -> findUser()); Future<Integer> order = scope.fork(() -> fetchOrder()); scope.join(); // 等待所有子任务 scope.throwIfFailed(); // 异常传播 return new Response(user.get(), order.get()); }