Java多线程高频面试题

17 阅读6分钟

一、基础概念题

1. 进程和线程区别?

  1. 进程:资源分配最小单位,独立内存、文件句柄,进程间隔离,切换开销大。
  2. 线程:CPU 调度最小单位,共享进程堆 / 全局变量,独有栈 / 程序计数器,切换开销小。
  3. 关系:一个进程至少 1 个主线程;进程崩溃会杀死所有线程,单一线程崩溃仅终止自身。
  4. IPC 进程通信:管道、socket、共享内存;线程通信直接共享变量。

2. 并发与并行区别

  • 并发:单核 CPU,时间片轮转,宏观同时执行,微观交替。
  • 并行:多核 CPU,多个线程真正同时运行。

3. 线程的 5 种状态(Java 标准)

新建 New → 就绪 Runnable → 运行 Running → 阻塞 Blocked → 终止 Terminated 阻塞细分:

  1. 同步阻塞:没抢到 synchronized 锁
  2. 等待阻塞:调用 wait (),释放锁
  3. 超时阻塞:sleep (long)/wait (long)

4. sleep ()、wait ()、yield ()、join () 区别

表格

方法所属类是否释放锁唤醒条件
sleepThread❌不释放时间到自动唤醒
waitObject✅释放锁notify/notifyAll/ 超时
yieldThread❌不释放让出 CPU,立刻回到就绪
joinThread不释放目标线程执行完毕

补充:

  • wait 必须写在 synchronized 代码块;sleep 任意位置。
  • yield 只是让出调度,不保证一定切换。

5. 为什么 wait/notify 定义在 Object 而不是 Thread?

锁是对象级别的,每个对象都有监视器锁;等待 / 唤醒是针对对象锁操作,不是线程。如果放在 Thread,一把锁对应多个线程无法实现。

二、锁与同步高频题

6. synchronized 底层原理、锁升级

  1. 底层:对象头 Mark Word、监视器锁 monitorenter/monitorexit 指令。
  2. 锁膨胀升级(不可逆): 偏向锁 → 轻量级锁(自旋) → 重量级锁(OS 内核互斥量)
  3. 三种用法:
  • 修饰实例方法:锁当前对象 this
  • 修饰静态方法:锁类 Class 对象
  • 同步代码块:手动指定锁对象

7. synchronized 和 Lock(ReentrantLock)对比

  1. synchronized:自动释放锁、非公平、不可中断、不支持条件变量。
  2. ReentrantLock:手动 lock/unlock、支持公平 / 非公平锁、可中断、多 Condition、可尝试获取锁 tryLock。
  3. 相同:都可重入。

8. 可重入锁是什么?为什么要可重入?

同一线程多次获取同一把锁不会死锁;比如同步方法 A 调用同步方法 B,不加可重入会自我阻塞。

9. 死锁产生 4 条件 + 如何避免

四条件(缺一不可):

  1. 互斥 2. 持有并等待 3. 不可剥夺 4. 循环等待 避免方案:
  • 统一锁获取顺序(最常用)
  • 设置超时 tryLock
  • 破坏持有等待,一次性获取所有锁

10. volatile 关键字作用

  1. 保证可见性:修改立即刷入主存,其他线程可见。
  2. 禁止指令重排(内存屏障)。
  3. ❌不保证原子性,不能替代锁做计数。 适用场景:状态标记、双重检查锁 DCL 单例。

11. volatile 为什么不能保证原子性?

i++ 分为 3 步:读 i → +1 → 写回;volatile 只能保证读写可见,中间计算会被多线程覆盖。 解决方案:AtomicInteger、synchronized、Lock。

12. Atomic 原子类原理

CAS(Compare And Swap)自旋乐观锁,底层 Unsafe 调用 CPU 指令 cmpxchg;无锁,性能高于 synchronized。 缺点:自旋空转消耗 CPU、ABA 问题。

13. ABA 问题怎么解决?

加版本号 AtomicStampedReference,每次修改版本 + 1,比较值 + 版本双重校验。

三、线程池核心面试(必问)

14. 为什么要用线程池?

  1. 降低创建销毁线程开销
  2. 控制并发数量,防止无限创建线程 OOM
  3. 统一管理线程、延迟 / 定时任务、复用线程

15. ThreadPoolExecutor 七大参数

  1. corePoolSize 核心线程数
  2. maximumPoolSize 最大线程数
  3. keepAliveTime 非核心空闲存活时间
  4. unit 时间单位
  5. workQueue 阻塞队列
  6. threadFactory 线程工厂(自定义线程名)
  7. handler 拒绝策略

16. 线程池执行流程

  1. 任务进来,核心线程未满 → 创建核心线程执行
  2. 核心满,任务放入阻塞队列
  3. 队列满,创建非核心线程到 maxPoolSize
  4. 达到最大线程,执行拒绝策略

17. 四种拒绝策略

  1. AbortPolicy(默认):直接抛 RejectedExecutionException
  2. DiscardPolicy:丢弃当前任务,无异常
  3. DiscardOldestPolicy:丢弃队列队首老任务,加入新任务
  4. CallerRunsPolicy:交给提交任务的主线程执行

18. JDK 内置 4 种线程池(Executors),阿里规范为什么禁止使用?

  1. newFixedThreadPool:无界队列,任务堆积 OOM
  2. newSingleThreadExecutor:无界队列
  3. newCachedThreadPool:最大线程 Integer.MAX_VALUE,线程无限创建 OOM
  4. newScheduledThreadPool:同样超大上限 规范:手动创建 ThreadPoolExecutor,自定义队列长度、拒绝策略。

19. 核心线程会被回收吗?

默认不会;allowCoreThreadTimeOut (true) 开启后,核心线程空闲超时也会销毁。

20. 如何合理设置线程池核心参数?

  • CPU 密集型:核心线程 = CPU 核心数 + 1
  • IO 密集型:核心线程 = 2 * CPU 核心数 或 核心数 /(1 - 阻塞系数)
  • 业务区分:数据库、RPC、文件 IO 属于 IO 密集

四、并发工具类

21. CountDownLatch、CyclicBarrier、Semaphore 区别

  1. CountDownLatch:一次性,主线程等待 N 个线程完成,不可重置。
  2. CyclicBarrier:可循环复用,N 个线程互相等待全部到达屏障再一起执行。
  3. Semaphore:信号量,控制同一时间并发线程数量(限流)。

22. ThreadLocal 原理、内存泄漏

  1. 底层:Thread 内部 ThreadLocalMap,key 弱引用 ThreadLocal,value 强引用。
  2. 内存泄漏原因:Thread 长期存活(线程池),ThreadLocal 对象被回收,但 value 无法释放。
  3. 解决:用完调用 remove ();避免在线程池滥用。

五、手写代码面试题(手撕)

1. 两个线程交替打印 1-100(wait/notify/ Lock+Condition)

2. DCL 双重检查锁单例,volatile 作用

public class Singleton {
    private volatile static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile 禁止重排,防止半初始化对象暴露。

3. 线程安全计数器(synchronized / AtomicInteger / Lock)

4. 手写生产者消费者模型

5. 手写简易线程池(拓展题)

六、综合场景 & 项目面试题

  1. 接口高并发下超卖问题解决方案 分布式锁 Redisson、数据库乐观锁、队列削峰
  2. 线上线程池任务堆积如何排查? 打印线程栈 jstack、监控队列长度、调大核心线程 / 扩容队列
  3. 多线程下 HashMap 为什么死循环?JDK1.8 怎么优化? 1.7 扩容头插法链表循环;1.8 尾插 + 红黑树,无死循环,但仍线程不安全。并发用 ConcurrentHashMap。
  4. ConcurrentHashMap 分段锁 / 锁粒度 JDK1.7 分段 Segment;1.8 锁头节点 + CAS,粒度更细,支持并发读写。
  5. CompletableFuture 异步多线程编排(thenApply、allOf)

七、反问面试官(加分)

  1. 项目中 IO 密集还是 CPU 密集?线程池参数如何配置?
  2. 分布式场景下多线程如何保证数据一致性?
  3. 线上并发问题排查工具栈(jstack、jmap、arthas)