1. Java并发面试题100道解析:从基础到实战
作为一名经历过上百场技术面试的Java老兵,我深知并发编程在面试中的分量。这份精心整理的100道Java并发面试题,不仅覆盖了面试高频考点,更融入了我多年实战中积累的经验和教训。无论你是准备跳槽涨薪,还是想系统提升并发编程能力,这份指南都能帮你少走弯路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线程基础:理解并发编程的基石
2.1 线程与进程的本质区别
很多面试者能背出"进程是资源分配单位,线程是CPU调度单位"的标准答案,但真正理解其内涵的人不多。在实际开发中,我经常看到因为混淆两者概念导致的性能问题。
进程拥有独立的地址空间,这意味着:
- 进程间通信(IPC)成本高(需要管道、共享内存等机制)
- 进程切换开销大(需要保存/恢复完整的上下文)
- 进程崩溃不会影响其他进程(天然的隔离性)
而线程共享进程资源:
- 通信简单(直接读写共享变量即可)
- 切换成本低(只需保存程序计数器等少量寄存器)
- 一个线程崩溃可能导致整个进程退出(缺乏隔离性)
实际案例:我曾优化过一个日志服务,原设计为每个客户端连接创建独立进程,导致服务器在300连接时就资源耗尽。改为线程模型后,轻松支持5000+并发连接。
2.2 四种线程创建方式深度对比
继承Thread类
java复制class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
// 启动
new MyThread().start();
- 优点:写法简单
- 缺点:Java单继承特性导致扩展性差
实现Runnable接口
java复制class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
// 启动
new Thread(new MyRunnable()).start();
- 优点:可继承其他类,适合多线程共享同一任务
- 缺点:无法直接获取执行结果
实现Callable接口
java复制class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
// 启动
FutureTask<String> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
System.out.println(future.get()); // 获取结果
- 优点:可返回结果,可抛出异常
- 缺点:使用稍复杂
线程池方式(推荐)
java复制ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("Pool running"));
executor.shutdown();
- 优点:资源可控,避免频繁创建销毁线程
- 缺点:需要理解线程池参数配置
踩坑记录:早期项目我曾大量直接new Thread(),导致线上出现数千线程竞争CPU,系统完全卡死。改用线程池后性能提升300%。
2.3 线程生命周期与状态转换
线程状态流转图:
code复制新建(NEW) → 就绪(RUNNABLE) ↔ 运行(RUNNING)
↓
阻塞(BLOCKED)
↓
等待(WAITING)
↓
超时等待(TIMED_WAITING)
↓
终止(TERMINATED)
关键状态解析:
- BLOCKED:等待获取监视器锁(如synchronized)
- WAITING:无限期等待(如object.wait())
- TIMED_WAITING:有限期等待(如Thread.sleep())
查看线程状态的方法:
java复制Thread thread = new Thread(()->{...});
thread.start();
System.out.println(thread.getState()); // 获取当前状态
2.4 sleep()与wait()的九大区别
| 特性 | sleep() | wait() |
|---|---|---|
| 所属类 | Thread | Object |
| 锁释放 | 不释放 | 释放 |
| 唤醒条件 | 时间到期 | notify()/notifyAll() |
| 使用场景 | 定时任务 | 线程间协调 |
| 异常 | InterruptedException | InterruptedException |
| 静态方法 | 是 | 否 |
| 调用前提 | 任何情况 | 必须持有锁 |
| 精度控制 | 支持纳秒 | 只支持毫秒 |
| JVM实现 | 原生支持 | 依赖监视器 |
典型错误案例:
java复制synchronized(lock) {
Thread.sleep(1000); // 错误!持有锁睡觉会导致其他线程阻塞
lock.wait(1000); // 正确!等待时会释放锁
}
3. 锁机制:高并发环境下的守护者
3.1 synchronized的底层实现原理
每个Java对象都有个隐藏的Monitor(监视器),synchronized正是通过它实现同步:
- 字节码层面:通过monitorenter和monitorexit指令实现
- JVM层面:依赖操作系统的互斥锁(Mutex Lock)
- 内存语义:遵循happens-before原则,保证可见性
对象头结构(64位JVM):
code复制|------------------------------------------------------------------|
| Mark Word (64bits) | Klass Word (64bits) |
|--
