Java并发编程高频面试题大全(2024最新版)
Java并发编程是面试中的重头戏,几乎每场Java面试都会涉及。本文精心整理了最常见、最高频的并发面试题,覆盖从基础到进阶的各个层面,帮助大家系统复习,从容应对面试。
一、线程基础
1. 线程和进程的区别是什么?
| 对比项 | 进程 | 线程 | |--------|------|------| | 定义 | 程序的一次执行过程 | 进程内的一个执行单元 | | 资源 | 拥有独立的内存空间 | 共享进程的资源 | | 开销 | 创建和切换开销大 | 创建和切换开销小 | | 通信 | 需要IPC机制(管道、消息队列等) | 可以直接共享内存通信 | | 影响 | 一个进程崩溃不影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |
2. 创建线程有哪几种方式?
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口(配合 FutureTask,可以有返回值)
- 使用线程池(Executor 框架)
// 方式1:继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread方式");
}
}
// 方式2:实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable方式");
}
}
// 方式3:实现Callable接口
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable方式";
}
}
// 方式4:线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(() -> System.out.println("线程池方式"));
3. 线程的生命周期(状态)有哪些?
Java线程有 6种状态(Thread.State枚举):
- NEW:新建,线程对象已创建但未调用start()
- RUNNABLE:可运行,包括就绪(等待CPU)和运行中
- BLOCKED:阻塞,等待获取监视器锁
- WAITING:等待,调用wait()/join()/LockSupport.park()等
- TIMED_WAITING:超时等待,调用sleep(ms)/wait(ms)/join(ms)等
- TERMINATED:终止,线程执行完毕或异常退出
4. start()和run()的区别?
- 调用 start() 会创建一个新的线程,并让该线程执行run()方法中的代码
- 直接调用 run() 不会创建新线程,只是在当前线程中执行run()方法体
- 一个线程只能调用一次start(),重复调用会抛出IllegalThreadStateException
二、synchronized 关键字
5. synchronized 的使用方式和原理?
三种使用方式:
- 修饰实例方法:锁的是当前实例对象
- 修饰静态方法:锁的是当前类的Class对象
- 修饰代码块:锁的是指定对象
底层原理:
- 同步代码块:通过
monitorenter和monitorexit指令实现 - 同步方法:通过方法访问标志
ACC_SYNCHRONIZED实现 - 本质都是基于 Monitor(监视器锁) 实现
6. synchronized 锁升级过程?
JDK 1.6 引入了锁优化机制,锁的升级过程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
| 锁类型 | 适用场景 | 特点 | |--------|----------|------| | 偏向锁 | 只有一个线程访问同步块 | 在对象头中记录线程ID,无需CAS | | 轻量级锁 | 交替执行同步块 | 通过CAS竞争,失败则自旋 | | 重量级锁 | 竞争激烈 | 阻塞等待,涉及用户态和内核态切换 |
注意:锁升级是单向的,不可降级(GC时可降级)。
三、volatile 关键字
7. volatile 的作用和原理?
两大作用:
- 保证可见性:一个线程修改了volatile变量,其他线程能立即看到最新值
- 禁止指令重排序:通过内存屏障(Memory Barrier)实现
底层原理:
- 写操作:在写指令后插入 StoreLoad 屏障
- 读操作:在读指令前插入 LoadLoad 屏障
- 底层使用 lock前缀指令,将当前CPU缓存行的数据写回主内存,并使其他CPU中该变量的缓存行失效
8. volatile 能保证线程安全吗?
不能完全保证! volatile 只保证了可见性和有序性,不保证原子性。
// 经典反例:volatile不能保证i++的原子性
public class Counter {
private volatile int count = 0;
public void increment() {
count++; // 非原子操作:读取→加1→写回
}
}
i++ 实际上是三步操作(读→改→写),需要使用 AtomicInteger 或 synchronized 来保证原子性。
四、CAS 与原子类
9. 什么是 CAS?有什么问题?
CAS(Compare And Swap):比较并交换,是一种无锁的并发控制方式。
// CAS 伪代码
boolean cas(V, expectedValue, newValue) {
if (V == expectedValue) {
V = newValue;
return true;
}
return false;
}
CAS 存在的问题:
- ABA问题:值从A→B→A,CAS认为没有变化。解决:使用
AtomicStampedReference(带版本号) - 自旋开销:CAS长时间不成功会一直循环,消耗CPU
- 只能保证一个共享变量的原子操作:解决:使用
AtomicReference将多个变量封装成一个对象
10. 常用的原子类有哪些?
| 类名 | 说明 | |------|------| | AtomicInteger | 整型原子类 | | AtomicLong | 长整型原子类 | | AtomicBoolean | 布尔型原子类 | | AtomicReference | 引用类型原子类 | | AtomicStampedReference | 带版本号的引用类型(解决ABA问题) | | LongAdder | JDK8新增,高并发下性能优于AtomicLong |
五、线程池
11. 为什么要使用线程池?
- 降低资源消耗:重复利用已创建的线程,减少线程创建和销毁的开销
- 提高响应速度:任务到达时无需等待线程创建即可执行
- 提高可管理性:统一管理线程,避免无限制创建线程导致OOM
12. ThreadPoolExecutor 的核心参数有哪些?
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
执行流程:
提交任务 → 核心线程未满?→ 创建核心线程执行
→ 核心线程满,队列未满?→ 放入队列等待
→ 队列满,未达最大线程数?→ 创建非核心线程执行
→ 达到最大线程数且队列满?→ 执行拒绝策略
13. 四种拒绝策略是什么?
| 拒绝策略 | 说明 | |----------|------| | AbortPolicy(默认) | 抛出RejectedExecutionException异常 | | CallerRunsPolicy | 由提交任务的线程自己执行该任务 | | DiscardPolicy | 直接丢弃任务,不抛异常 | | DiscardOldestPolicy | 丢弃队列中最老的任务,重新提交新任务 |
14. 为什么不推荐使用 Executors 创建线程池?
// 不推荐!
Executors.newFixedThreadPool(); // 队列是Integer.MAX_VALUE,可能OOM
Executors.newSingleThreadExecutor(); // 队列是Integer.MAX_VALUE,可能OOM
Executors.newCachedThreadPool(); // 最大线程数Integer.MAX_VALUE,可能OOM
阿里Java开发手册明确规定:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,避免资源耗尽风险。
六、AQS 与 Lock
15. 什么是 AQS?
AQS(AbstractQueuedSynchronizer) 是 Java 并发包的核心框架,是构建锁和同步器的基础。
核心思想:
- 使用一个 volatile int state 变量表示同步状态
- 通过 CLH队列(FIFO双向链表)管理等待获取锁的线程
- 提供 模板方法,子类重写
tryAcquire()/tryRelease()等实现不同语义
基于AQS实现的同步器:
- ReentrantLock
- ReentrantReadWriteLock
- CountDownLatch
- Semaphore
- CyclicBarrier
16. synchronized 和 ReentrantLock 的区别?
| 对比项 | synchronized | ReentrantLock | |--------|-------------|---------------| | 实现方式 | JVM层面(关键字) | API层面(类) | | 锁的获取 | 自动获取 | 手动lock() | | 锁的释放 | 自动释放 | 手动unlock()(必须在finally中释放) | | 可中断 | 不可中断 | 可中断(lockInterruptibly()) | | 公平性 | 非公平锁 | 可选公平/非公平 | | 条件变量 | 只有一个wait/notify | 支持多个Condition | | 可重入 | 是 | 是 |
七、并发工具类
17. CountDownLatch 和 CyclicBarrier 的区别?
| 对比项 | CountDownLatch | CyclicBarrier | |--------|---------------|---------------| | 计数方式 | 递减计数 | 递减到0后可重置 | | 触发方式 | 一个线程等待其他线程完成 | 多个线程互相等待 | | 可重用 | 不可重用(一次性) | 可重用(自动重置) | | 使用场景 | 主线程等待子任务完成 | 多线程同时开始执行 |
// CountDownLatch 示例:主线程等待3个子线程完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 执行任务
latch.countDown();
}).start();
}
latch.await(); // 等待所有子线程完成
System.out.println("所有任务完成");
18. Semaphore 是什么?
Semaphore(信号量) 用于控制同时访问特定资源的线程数量,可以用来实现限流。
// 只允许3个线程同时访问
Semaphore semaphore = new Semaphore(3);
semaphore.acquire(); // 获取许可
try {
// 访问资源
} finally {
semaphore.release(); // 释放许可
}
八、ThreadLocal
19. ThreadLocal 的原理是什么?会有什么问题?
原理:
- 每个Thread对象内部有一个
ThreadLocalMap成员变量 - ThreadLocal的set/get操作实际上是在当前线程的ThreadLocalMap中存取数据
- key是ThreadLocal对象的弱引用,value是存储的值
内存泄漏问题:
- ThreadLocalMap 的 key 是弱引用,GC时key可能被回收,但value是强引用
- 导致 key 为 null 的 Entry,value 无法被回收
- 解决方法: 使用完后务必调用
threadLocal.remove()清除数据
九、线程间通信
20. 线程间通信有哪些方式?
- volatile:通过共享内存实现可见性
- wait/notify:Object类的方法,需要配合synchronized使用
- Lock/Condition:更灵活的等待/通知机制
- CountDownLatch/CyclicBarrier:并发工具类
- BlockingQueue:阻塞队列
- 管道流:PipedOutputStream / PipedInputStream
十、实战场景题
21. 如何实现一个生产者-消费者模型?
// 使用 BlockingQueue 实现(最简洁)
public class ProducerConsumer {
private BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
public void produce() throws InterruptedException {
queue.put("product"); // 队列满时阻塞
}
public void consume() throws InterruptedException {
queue.take(); // 队列空时阻塞
}
}
22. 如何控制三个线程按顺序执行?
// 方式1:使用join()
Thread t1 = new Thread(() -> System.out.println("T1"));
Thread t2 = new Thread(() -> { t1.join(); System.out.println("T2"); });
Thread t3 = new Thread(() -> { t2.join(); System.out.println("T3"); });
t1.start(); t2.start(); t3.start();
// 方式2:使用CountDownLatch
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
new Thread(() -> {
System.out.println("T1");
latch1.countDown();
}).start();
new Thread(() -> {
latch1.await();
System.out.println("T2");
latch2.countDown();
}).start();
new Thread(() -> {
latch2.await();
System.out.println("T3");
}).start();
总结
Java并发编程的知识点非常多,面试中最核心的考察方向是:
- ✅ 线程基础:创建方式、生命周期
- ✅ 锁机制:synchronized锁升级、ReentrantLock、AQS原理
- ✅ volatile:可见性、有序性、不保证原子性
- ✅ CAS:原理与ABA问题
- ✅ 线程池:核心参数、执行流程、拒绝策略
- ✅ 并发工具:CountDownLatch、CyclicBarrier、Semaphore
- ✅ ThreadLocal:原理与内存泄漏
建议大家在理解原理的基础上,多动手写代码验证,这样才能在面试中应对自如!
💡 如果觉得本文有帮助,欢迎点赞收藏,祝你面试顺利!
926

被折叠的 条评论
为什么被折叠?



