前言

Java 多线程是 Java 后端面试中非常高频的一块内容。

平时写业务代码时,可能不会天天手写线程,但在面试中,经常会被问到:

  • 线程和进程有什么区别?
  • 创建线程有哪几种方式?
  • sleep() 和 wait() 有什么区别?
  • synchronized 和 volatile 有什么区别?
  • CAS 是什么?
  • ThreadLocal 是什么?
  • 线程池七大参数是什么?
  • 线程池执行流程是什么?
  • 死锁怎么产生?怎么解决?

这篇文章整理一份 Java 多线程高频八股文,适合初学者和准备面试的同学快速复习。


1. 什么是进程和线程?

进程

进程是操作系统分配资源的基本单位。

一个正在运行的程序,就可以看作一个进程。

比如:

运行中的 IDEA
运行中的 MySQL
运行中的浏览器

线程

线程是 CPU 调度的基本单位。

一个进程中可以包含多个线程。

比如浏览器进程中可能有:

页面渲染线程
网络请求线程
JS 执行线程

简单理解:

进程是资源分配单位,线程是程序执行单位。


2. 进程和线程有什么区别?

对比项 进程 线程
资源 拥有独立资源 共享进程资源
开销 创建和切换开销大 创建和切换开销小
通信 通信比较复杂 通信比较方便
稳定性 一个进程崩溃通常不影响其他进程 一个线程异常可能影响整个进程

面试时可以这样回答:

进程是资源分配的基本单位,线程是 CPU 调度的基本单位。一个进程可以包含多个线程,线程之间共享进程的内存资源,所以线程通信更方便,但也更容易产生线程安全问题。


3. 创建线程有哪几种方式?

Java 中常见创建线程的方式有四种:

继承 Thread 类

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

使用:

new MyThread().start();

实现 Runnable 接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

使用:

new Thread(new MyRunnable()).start();

实现 Callable 接口

class MyCallable implements Callable<String> {
    @Override
    public String call() {
        return "执行结果";
    }
}

Callable 可以有返回值,也可以抛出异常。

使用线程池

ExecutorService executorService = Executors.newFixedThreadPool(5);

executorService.submit(() -> {
    System.out.println("线程池执行任务");
});

实际开发中更推荐使用线程池,而不是频繁手动创建线程。


4. Runnable 和 Callable 有什么区别?

对比项 Runnable Callable
返回值 没有返回值 有返回值
方法 run() call()
异常 不能直接抛出受检异常 可以抛出异常
配合结果 不返回结果 通常配合 Future 获取结果

Runnable 示例:

Runnable runnable = () -> System.out.println("执行任务");

Callable 示例:

Callable<String> callable = () -> "执行结果";

简单总结:

Runnable 适合不需要返回结果的任务,Callable 适合需要返回结果的任务。


5. start() 和 run() 有什么区别?

start() 是启动线程。

调用 start() 后,JVM 会创建一个新的线程,并让这个线程去执行 run() 方法。

new Thread(() -> {
    System.out.println("执行任务");
}).start();

run() 只是一个普通方法。

如果直接调用 run(),不会创建新线程,而是在当前线程中执行。

thread.run();

面试回答:

start() 会真正启动一个新线程,run() 只是普通方法调用,不会开启新线程。


6. 线程有哪些状态?

Java 线程常见状态有:

NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

NEW

线程刚创建,还没有调用 start()。

RUNNABLE

线程已经启动,正在运行或等待 CPU 调度。

BLOCKED

线程等待获取锁。

WAITING

线程无限期等待,需要其他线程唤醒。

TIMED_WAITING

线程限时等待,比如 sleep()。

TERMINATED

线程执行结束。


7. sleep() 和 wait() 有什么区别?

对比项 sleep() wait()
所属类 Thread Object
是否释放锁 不释放锁 释放锁
使用位置 任意位置 必须在同步代码块或同步方法中
唤醒方式 时间到了自动恢复 notify 或 notifyAll 唤醒

示例:

Thread.sleep(1000);

wait() 示例:

synchronized (lock) {
    lock.wait();
}

面试重点:

sleep 不会释放锁,wait 会释放锁。


8. notify() 和 notifyAll() 有什么区别?

notify() 会随机唤醒一个等待该对象锁的线程。

lock.notify();

notifyAll() 会唤醒所有等待该对象锁的线程。

lock.notifyAll();

需要注意:

被唤醒的线程不会立刻执行,而是要重新竞争锁,拿到锁后才能继续执行。

实际开发中,如果不确定唤醒哪个线程更合适,通常使用 notifyAll() 更稳。


9. 什么是线程安全问题?

线程安全问题指的是:

多个线程同时操作共享数据,导致结果不符合预期。

例如:

count++;

这行代码看起来只有一步,实际上可能包含:

读取 count
count 加 1
写回 count

如果多个线程同时执行,就可能出现数据覆盖,导致结果错误。

解决线程安全问题的常见方式有:

synchronized
Lock
volatile
Atomic 原子类
ThreadLocal

10. synchronized 是什么?

synchronized 是 Java 提供的关键字,用来保证线程安全。

它可以修饰:

实例方法

public synchronized void method() {
}

锁的是当前对象 this。

静态方法

public static synchronized void method() {
}

锁的是当前类的 Class 对象。

代码块

synchronized (lock) {
}

锁的是括号中的对象。

synchronized 可以保证:

  1. 原子性。
  2. 可见性。
  3. 有序性。

11. synchronized 的底层原理是什么?

synchronized 底层主要依赖对象监视器 Monitor。

每个 Java 对象都可以作为锁对象。

当线程进入同步代码块时,需要先获取对象的 Monitor。

如果获取成功,就可以执行同步代码。

如果获取失败,就会进入阻塞状态,等待锁释放。

简单理解:

synchronized 是通过对象锁和 Monitor 机制实现线程同步的。


12. synchronized 锁升级是什么?

在 JDK 6 之后,synchronized 做了很多优化。

锁状态大致包括:

无锁 偏向锁 轻量级锁 重量级锁

锁升级过程一般是:

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

偏向锁

适合只有一个线程访问同步代码的场景。

轻量级锁

适合多个线程交替访问,但竞争不激烈的场景。

重量级锁

适合线程竞争激烈的场景,会涉及线程阻塞和唤醒,开销较大。


13. volatile 是什么?

volatile 是 Java 中的关键字,主要作用有两个:

  1. 保证变量的可见性。
  2. 禁止指令重排序。

示例:

private volatile boolean flag = true;

当一个线程修改了 flag,其他线程可以立即看到最新值。

但是需要注意:

volatile 不能保证复合操作的原子性。

例如:

count++;

即使 count 使用了 volatile 修饰,也不能保证线程安全。


14. volatile 和 synchronized 有什么区别?

对比项 volatile synchronized
原子性 不保证 保证
可见性 保证 保证
有序性 一定程度保证 保证
阻塞 不会阻塞线程 可能阻塞线程
使用场景 状态标记 复杂同步操作

简单总结:

volatile 更轻量,适合状态标记;synchronized 更强大,适合需要保证原子性的代码块。


15. 什么是 CAS?

CAS 全称是 Compare And Swap,中文叫比较并交换。

它是一种无锁的原子操作。

CAS 包含三个值:

内存值 V 预期值 A 新值 B

执行逻辑是:

如果 V == A,就把 V 改成 B 如果 V != A,说明数据被别人改过,操作失败

Java 中很多原子类都使用了 CAS,例如:

AtomicInteger
AtomicLong
AtomicReference

16. CAS 有什么问题?

CAS 虽然效率高,但也有一些问题:

ABA 问题

一个值从 A 变成 B,又变回 A。

CAS 只判断值是否相等,无法知道中间是否被修改过。

解决方式:

AtomicStampedReference

通过版本号解决 ABA 问题。

自旋开销大

CAS 失败后可能会不断重试,如果竞争激烈,会消耗 CPU。

只能保证单个变量的原子操作

如果要保证多个变量的一致性,CAS 处理起来比较复杂。


17. AtomicInteger 为什么线程安全?

AtomicInteger 底层主要使用 CAS 实现线程安全。

例如:

AtomicInteger count = new AtomicInteger(0);

count.incrementAndGet();

多个线程同时执行自增时,AtomicInteger 会不断比较旧值并尝试更新。

如果更新失败,就重新获取最新值再尝试。

所以它可以在不加锁的情况下保证自增操作的原子性。


18. 什么是 ThreadLocal?

ThreadLocal 可以为每个线程保存一份独立的变量副本。

不同线程之间互不影响。

示例:

ThreadLocal<String> threadLocal = new ThreadLocal<>();

threadLocal.set("user");
String value = threadLocal.get();
threadLocal.remove();

常见使用场景:

  1. 保存当前登录用户信息。
  2. 保存数据库连接。
  3. 保存请求上下文。
  4. 避免参数层层传递。

简单理解:

ThreadLocal 是线程级别的变量隔离工具。


19. ThreadLocal 底层原理是什么?

每个线程对象内部都有一个 ThreadLocalMap。

当调用:

threadLocal.set(value);

实际上是把数据存到当前线程的 ThreadLocalMap 中。

其中:

key 是 ThreadLocal 对象 value 是存储的值

不同线程有不同的 ThreadLocalMap,所以数据互不影响。


20. ThreadLocal 为什么可能内存泄漏?

ThreadLocalMap 中的 key 是弱引用,value 是强引用。

如果 ThreadLocal 对象被回收,key 会变成 null。

但是 value 还被 ThreadLocalMap 强引用着,可能无法释放。

尤其在线程池中,线程会被复用,如果不清理 ThreadLocal,value 可能长期存在。

所以使用完 ThreadLocal 后,建议调用:

threadLocal.remove();

21. 什么是线程池?

线程池就是提前创建并管理一批线程。

当有任务提交时,不需要每次都创建新线程,而是复用已有线程执行任务。

线程池的好处:

  1. 减少线程创建和销毁开销。
  2. 提高响应速度。
  3. 统一管理线程。
  4. 防止线程无限创建导致系统资源耗尽。

实际开发中,推荐使用线程池管理异步任务。


22. 为什么不建议使用 Executors 创建线程池?

例如:

Executors.newFixedThreadPool(10);
Executors.newCachedThreadPool();

虽然写法简单,但不太推荐。

原因是:

  1. newFixedThreadPool 使用的队列可能非常大,容易造成 OOM。
  2. newCachedThreadPool 最大线程数可能非常大,容易创建过多线程。
  3. 参数不够明确,不利于排查问题。

实际项目中更推荐手动创建 ThreadPoolExecutor。


23. 线程池七大参数是什么?

ThreadPoolExecutor 有七个核心参数:

new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    unit,
    workQueue,
    threadFactory,
    handler
);

分别是:

corePoolSize

核心线程数。

maximumPoolSize

最大线程数。

keepAliveTime

非核心线程空闲存活时间。

unit

时间单位。

workQueue

任务队列。

threadFactory

线程工厂,用来创建线程。

handler

拒绝策略。

这七个参数是线程池面试必背内容。


24. 线程池执行流程是什么?

线程池执行任务的大致流程:

  1. 如果当前线程数小于核心线程数,创建核心线程执行任务。
  2. 如果核心线程已满,把任务放入任务队列。
  3. 如果任务队列已满,并且当前线程数小于最大线程数,创建非核心线程执行任务。
  4. 如果最大线程数也满了,就执行拒绝策略。

可以简单记成:

核心线程 -> 任务队列 -> 最大线程 -> 拒绝策略


25. 线程池有哪些拒绝策略?

常见拒绝策略有四种:

AbortPolicy

默认策略,直接抛出异常。

CallerRunsPolicy

由提交任务的线程自己执行任务。

DiscardPolicy

直接丢弃任务,不抛异常。

DiscardOldestPolicy

丢弃队列中最早的任务,然后尝试提交当前任务。

实际开发中,拒绝策略要根据业务场景选择,不能随便丢任务。


26. execute() 和 submit() 有什么区别?

对比项 execute() submit()
所属接口 Executor ExecutorService
返回值 没有返回值 返回 Future
异常表现 异常直接抛出 异常封装在 Future 中
任务类型 Runnable Runnable 或 Callable

如果不需要返回结果,可以使用:

execute()

如果需要返回结果,可以使用:

submit()


27. 什么是死锁?

死锁指的是多个线程互相等待对方释放资源,导致程序一直无法继续执行。

例如:

线程 A 拿着锁 1,等待锁 2 线程 B 拿着锁 2,等待锁 1

这样两个线程就会一直等待下去。


28. 死锁产生的条件有哪些?

死锁产生通常需要满足四个条件:

  1. 互斥条件。
  2. 请求并保持条件。
  3. 不可剥夺条件。
  4. 循环等待条件。

只要破坏其中一个条件,就可以避免死锁。

常见解决方式:

  1. 固定加锁顺序。
  2. 避免嵌套锁。
  3. 使用超时锁。
  4. 减小锁粒度。
  5. 使用工具排查死锁。

29. 如何排查死锁?

可以使用 JDK 自带工具:

jps jstack

先查看 Java 进程:

jps

再查看线程堆栈:

jstack 进程ID

如果存在死锁,线程堆栈中通常会有相关提示。

也可以使用:

VisualVM Arthas JConsole

这些工具辅助排查线程问题。


30. Java 多线程面试怎么回答更稳?

回答多线程问题时,不要只背一个概念。

建议按照:

是什么 + 为什么 + 怎么用 + 注意点

比如问:“volatile 是什么?”

可以这样回答:

volatile 是 Java 中的关键字,主要作用是保证变量的可见性和禁止指令重排序。一个线程修改 volatile 变量后,其他线程可以及时看到最新值。但 volatile 不能保证复合操作的原子性,比如 count++ 仍然不是线程安全的。如果需要保证原子性,可以使用 synchronized、Lock 或 Atomic 原子类。

再比如问:“线程池执行流程是什么?”

可以这样回答:

线程池收到任务后,会先判断当前线程数是否小于核心线程数,如果小于就创建核心线程执行任务;如果核心线程已满,就把任务放入阻塞队列;如果队列也满了,并且线程数还没达到最大线程数,就创建非核心线程;如果最大线程数也满了,就执行拒绝策略。


总结

Java 多线程是后端面试中的重点内容,常见考点主要包括:

线程和进程 线程创建方式 线程生命周期 sleep 和 wait synchronized volatile CAS Atomic 原子类 ThreadLocal 线程池 死锁

其中最重要的一定是:

  1. 线程和进程的区别。
  2. 创建线程的方式。
  3. sleep 和 wait 的区别。
  4. synchronized 的使用和原理。
  5. volatile 的作用。
  6. CAS 的原理和问题。
  7. ThreadLocal 的原理和内存泄漏。
  8. 线程池七大参数和执行流程。
  9. 死锁产生和解决。

如果是初学者,建议先把这些问题理解清楚,再结合代码练习。面试时不用一上来就讲源码细节,但一定要能把核心概念、使用场景和注意点说清楚。

 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐