```html
欢迎来到线程面试,您的并发编程备考专家 | 域名: www.kaoacca.cn

线程面试全攻略:从基础原理到并发实战

深入解析Java多线程面试核心考点,涵盖线程生命周期、状态转换、JUC并发工具包、锁机制、线程池参数及调优策略。 提供高频面试题详解与代码示例,助你顺利通过大厂面试。

一、为什么线程面试是后端开发的必考题?

在Java后端开发的面试体系中,线程面试往往是区分初级与高级开发者的关键分水岭。随着互联网业务的高并发特性日益显著,如何高效利用多核CPU、保证数据一致性、处理线程安全问题,成为了企业考察候选人技术深度的核心维度。

许多求职者往往只停留在“知道怎么创建线程”的层面,而在面对“线程池参数如何配置”、“死锁如何排查”、“CAS底层原理”等深度问题时束手无策。本页面旨在通过结构化的知识梳理,帮助开发者建立完整的线程面试知识图谱。

⚡ 高频考点分布

  • 线程状态与生命周期 (100% 必问)
  • ThreadLocal原理与内存泄漏 (85% 高频)
  • 线程池核心参数与拒绝策略 (90% 必问)
  • synchronized与Lock的区别 (80% 高频)
  • volatile关键字与内存可见性 (75% 高频)

⚙️ 面试官视角

面试官考察线程面试不仅仅是为了确认你背过八股文,更是为了验证你在实际项目中是否具备处理并发问题的能力。例如,他们可能会问:“你在项目中遇到过什么并发问题?是如何解决的?” 这就要求我们不仅要懂理论,还要懂实战。

二、线程基础:生命周期与状态转换

理解线程面试中的基础概念是第一步。Java线程的生命周期通常包含六种状态:NEW(新建)、RUNNABLE(可运行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(超时等待)、TERMINATED(终止)。

2.1 状态详解与转换

状态名称 触发条件 对应方法/关键字 面试考点
NEW 线程被创建,但未启动 new Thread() 如何判断线程是否启动?
RUNNABLE 调用start()后,等待CPU调度或正在运行 start() RUNNABLE包含操作系统层面的Running和Ready
BLOCKED 等待获取synchronized监视器锁 synchronized 与WAITING的区别:是否有超时时间
WAITING 无限期等待其他线程唤醒 wait(), join() 不会释放锁?错,wait()会释放锁
TERMINATED 线程执行完毕或异常退出 run()结束 终止的线程能再次start吗?不能

2.2 ThreadLocal:线程隔离的神器

在线程面试中,ThreadLocal是一个绕不开的话题。它主要用于实现线程封闭(Thread Confinement),即每个线程拥有变量的独立副本,避免线程同步问题。

public class ThreadLocalExample {
    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        // 设置当前线程的值
        threadLocal.set(100);
        // 获取当前线程的值
        System.out.println(threadLocal.get()); // 输出 100
        // 移除当前线程的值,防止内存泄漏
        threadLocal.remove();
    }
}

面试陷阱:ThreadLocalMap的Key是弱引用,Value是强引用。如果Key被GC回收,而Value没有被清除,会导致内存泄漏。因此,务必在使用后调用remove()方法。

三、JUC并发工具包:高级线程面试核心

随着JDK版本的演进,java.util.concurrent(JUC)包成为了线程面试的重中之重。相比传统的synchronized,JUC提供了更灵活、高性能的并发控制手段。

ReentrantLock
ReentrantReadWriteLock
Condition

ReentrantLock:可重入锁

ReentrantLock实现了Lock接口,提供了比synchronized更强大的功能。

  • 可中断锁:lockInterruptibly(),允许在等待锁时响应中断。
  • 尝试获取锁:tryLock(),避免死锁,可以设置超时时间。
  • 公平锁与非公平锁:synchronized是非公平的,而ReentrantLock默认是非公平的,但支持构造公平锁。
  • 多个条件变量:通过newCondition()可以创建多个Condition对象,实现更精细的线程通信。

ReentrantReadWriteLock:读写锁

在读多写少的场景下,读写锁能显著提升性能。它维护了一对锁,一个读锁(共享锁),一个写锁(独占锁)。

  • 读锁之间不互斥,允许多个线程同时读取。
  • 写锁与读锁、写锁与写锁互斥。
  • 面试考点:读写锁的降级(Write -> Read)与升级(Read -> Write,不支持)。

Condition:线程通信

Condition替代了Object的wait()/notify()机制,提供了更灵活的线程等待/通知。

  • await():释放锁并等待。
  • signal():唤醒一个等待线程。
  • signalAll():唤醒所有等待线程。
  • 优势:可以绑定多个Condition,实现精确唤醒,避免“惊群效应”。

四、线程池:生产环境的线程面试必杀技

在实际开发中,我们绝不应该手动创建线程,而应该使用线程池。线程池不仅能提高性能(复用线程),还能控制并发数量,保护系统。在线程面试中,线程池的配置和原理是考察的重点。

4.1 ThreadPoolExecutor 核心参数

参数名 类型 说明
corePoolSize int 核心线程数,线程池中长期存活的线程数
maximumPoolSize int 最大线程数,允许创建的最大线程数
keepAliveTime long 空闲线程存活时间,超过此时间的空闲线程会被回收
workQueue BlockingQueue 任务队列,存放等待执行的任务
threadFactory ThreadFactory 线程工厂,用于创建新线程,建议自定义以设置名称
handler RejectedExecutionHandler 拒绝策略,当队列满且线程数达最大时如何处理新任务

4.2 拒绝策略

4.3 线程池大小配置

线程池大小的配置取决于任务类型:

五、锁机制:从synchronized到JMM

锁是解决并发安全问题的核心。在线程面试中,面试官通常会深入考察锁的优化、底层原理以及内存模型。

5.1 synchronized 锁升级

在JDK 1.6之后,synchronized进行了大量优化,引入了锁升级概念。

阶段一:无锁

对象头中的Mark Word记录对象哈希码、分代年龄等。此时没有锁。

阶段二:偏向锁

当只有一个线程访问同步块时,进入同步块会给对象头和栈帧中的锁记录加上偏向锁。下次进入只需检查Mark Word是否指向当前线程。

阶段三:轻量级锁

当有第二个线程竞争偏向锁时,偏向锁升级为轻量级锁。通过CAS操作尝试获取锁,失败则自旋等待。

阶段四:重量级锁

当自旋次数超过阈值或竞争加剧时,轻量级锁升级为重量级锁。此时线程阻塞,操作系统介入。

5.2 CAS 与 ABA 问题

CAS(Compare And Swap)是无锁编程的基础。它包含三个操作数:内存位置、预期原值和新值。如果内存位置的值与预期原值相匹配,则将该位置更新为新值。

ABA问题:一个值从A变为B,又变回A,CAS检查时会认为没有变化。解决方案是使用AtomicStampedReference,版本号机制。

六、实战案例:面试官最爱问的场景题

理论掌握后,面试官往往会给出一个具体的业务场景,考察你的实战能力。以下是几个经典的线程面试案例。

案例一:如何实现一个线程安全的单例模式?

常见的有双重检查锁(DCL)和静态内部类。DCL需要注意volatile关键字,防止指令重排序。

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

案例二:如何保证三个线程按顺序执行?

可以使用CountDownLatch、join()或ReentrantLock+Condition。

// 使用CountDownLatch
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
    System.out.println("Thread 1");
    latch1.countDown();
});
Thread t2 = new Thread(() -> {
    try { latch1.await(); } catch (InterruptedException e) {}
    System.out.println("Thread 2");
    latch2.countDown();
});
Thread t3 = new Thread(() -> {
    try { latch2.await(); } catch (InterruptedException e) {}
    System.out.println("Thread 3");
});
t1.start(); t2.start(); t3.start();
```
斌知网
蜀ICP备2026017620号-5