Java多线程全面面试题及答案
一、Java线程基础
1. 线程创建与生命周期
Q1: Java中创建线程有哪几种方式?各有什么优缺点?
答案:
1. 继承Thread类:
- 优点:简单直接
- 缺点:Java单继承限制,不利于扩展
- 示例:
class MyThread extends Thread {
public void run() {
// 线程执行逻辑
}
}
2. 实现Runnable接口:
- 优点:避免单继承限制,更灵活
- 缺点:无法直接获取执行结果
- 示例:
class MyRunnable implements Runnable {
public void run() {
// 线程执行逻辑
}
}
3. 实现Callable接口:
- 优点:可以获取返回值,支持异常抛出
- 缺点:使用稍复杂
- 示例:
class MyCallable implements Callable<String> {
public String call() throws Exception {
return "执行结果";
}
}
4. 线程池创建:
- 优点:资源复用,管理方便
- 缺点:配置参数需要调优
- 示例:
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {...});
Q2: 详细描述线程的6种状态及转换条件
答案:
1. NEW(新建):
- 线程被创建但未调用start()
2. RUNNABLE(可运行):
- 调用start()后进入该状态
- 包含就绪(Ready)和运行中(Running)两种子状态
3. BLOCKED(阻塞):
- 等待获取监视器锁(synchronized)
- 只有其他线程释放锁后才能转换
4. WAITING(无限等待):
- 调用wait()/join()/LockSupport.park()
- 需要其他线程notify()/notifyAll()/unpark()
5. TIMED_WAITING(定时等待):
- 调用sleep(time)/wait(time)/join(time)
- 超时或收到通知后转换
6. TERMINATED(终止):
- 线程执行完毕或异常退出
状态转换图:
NEW → start() → RUNNABLE
RUNNABLE → 获取锁失败 → BLOCKED
RUNNABLE → wait() → WAITING
WAITING → notify() → BLOCKED
RUNNABLE → sleep(time) → TIMED_WAITING
TIMED_WAITING → 超时 → RUNNABLE
RUNNABLE → 执行结束 → TERMINATED
2. 线程基本操作
Q3: sleep()、wait()、yield()和join()方法的区别?
答案:
┌──────────┬────────────┬────────────┬────────────┬────────────┐
│ 方法 │ 所属类 │ 释放锁 │ 唤醒条件 │ 使用场景 │
├──────────┼────────────┼────────────┼────────────┼────────────┤
│ sleep() │ Thread │ 不释放 │ 时间到期 │ 定时暂停 │
│ wait() │ Object │ 释放 │ notify() │ 线程间协作 │
│ yield() │ Thread │ 不释放 │ 系统调度 │ 让出CPU │
│ join() │ Thread │ - │ 线程结束 │ 等待线程完成 │
└──────────┴────────────┴────────────┴────────────┴────────────┘
关键区别:
1. wait()必须在同步块中调用,sleep()可以在任何地方
2. wait()会释放对象锁,sleep()不会释放任何锁
3. yield()只是建议调度器让出CPU,不保证一定生效
4. join()底层是通过wait()实现的
二、线程安全与锁机制
1. synchronized关键字
Q4: synchronized的实现原理是什么?JDK1.6做了哪些优化?
答案:
实现原理:
1. 同步代码块:
- 使用monitorenter/monitorexit字节码指令
- 每个对象关联一个Monitor对象
2. 同步方法:
- 方法标志位添加ACC_SYNCHRONIZED
- 隐式使用当前对象的Monitor
JDK1.6优化:
1. 锁升级机制:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
2. 新增锁状态:
- 偏向锁:消除无竞争时的同步开销
- 轻量级锁:通过CAS避免阻塞
3. 适应性自旋:
根据上次自旋结果动态调整自旋次数
4. 锁粗化:
合并连续同步块减少锁操作
2. volatile关键字
Q5: volatile的作用和实现原理?它能保证原子性吗?
答案:
三大特性:
1. 可见性:写操作立即刷新到主内存
2. 有序性:禁止指令重排序
3. 不保证原子性
实现原理:
1. 内存屏障:
- 写操作前加StoreStore屏障
- 写操作后加StoreLoad屏障
- 读操作前加LoadLoad屏障
- 读操作后加LoadStore屏障
2. 缓存一致性协议(MESI)
原子性示例:
// 以下操作不是原子的
volatile int count = 0;
count++; // 实际是read-modify-write三步操作
适用场景:
1. 状态标志位
2. 双重检查锁定(DCL)
3. 观察者模式中的发布-订阅
三、JUC并发工具包
1. AQS原理
Q6: AbstractQueuedSynchronizer的实现原理?
答案:
核心组成:
1. volatile int state:同步状态
2. Node:双向CLH队列节点
3. ConditionObject:条件变量
工作流程:
1. 获取锁:
- tryAcquire()尝试获取
- 失败则加入队列自旋/阻塞
2. 释放锁:
- tryRelease()释放资源
- 唤醒后继节点
关键方法:
1. acquire()/release():独占模式
2. acquireShared()/releaseShared():共享模式
3. hasQueuedPredecessors():公平锁判断
应用实例:
ReentrantLock、CountDownLatch、Semaphore等都基于AQS实现
2. ConcurrentHashMap
Q7: ConcurrentHashMap在JDK7和JDK8中的实现有什么区别?
答案:
JDK7实现:
1. 分段锁(Segment继承ReentrantLock)
2. 默认16个段,并发度固定
3. 段内是HashEntry数组+链表
JDK8改进:
1. 取消分段锁,改用CAS+synchronized
2. 数据结构:数组+链表+红黑树
3. 并发控制:
- 初始化:CAS
- put操作:
a. 空桶:CAS插入
b. 非空:synchronized锁头节点
4. size计算:baseCount+CounterCell数组
性能对比:
JDK8的并发度更高,内存占用更小,查询效率提升(红黑树)
四、线程池
1. 核心参数
Q8: ThreadPoolExecutor的7个核心参数是什么?如何合理配置?
答案:
7大参数:
1. corePoolSize:核心线程数(常驻)
2. maximumPoolSize:最大线程数
3. keepAliveTime:空闲线程存活时间
4. unit:时间单位
5. workQueue:任务队列
6. threadFactory:线程工厂
7. handler:拒绝策略
配置建议:
1. CPU密集型:
coreSize = CPU核数 + 1
maxSize = coreSize
2. IO密集型:
coreSize = CPU核数 × 2
maxSize = coreSize × (1 + WT/ST)
(WT:平均等待时间, ST:平均服务时间)
3. 队列选择:
- 快速响应:SynchronousQueue
- 无界队列:LinkedBlockingQueue
- 有界队列:ArrayBlockingQueue
监控指标:
1. 活跃线程数
2. 队列堆积量
3. 最大线程使用量
2. 执行流程
Q9: 描述线程池的任务执行流程?
答案:
执行流程图:
新任务提交 →
1. 核心线程未满?→ 创建新线程执行
2. 核心线程已满?→ 入队列等待
3. 队列已满?→ 创建非核心线程执行
4. 达到maxSize?→ 执行拒绝策略
关键方法:
1. execute():提交任务入口
2. addWorker():创建新线程
3. getTask():从队列获取任务
拒绝策略:
1. AbortPolicy:抛出RejectedExecutionException
2. CallerRunsPolicy:调用者线程执行
3. DiscardPolicy:静默丢弃
4. DiscardOldestPolicy:丢弃队列最老任务
五、高级并发模式
1. Fork/Join框架
Q10: ForkJoinPool的工作原理?
答案:
设计特点:
1. 工作窃取(Work-Stealing)算法
2. 每个线程维护双端队列
3. 分治任务(ForkJoinTask)
使用步骤:
1. 继承RecursiveTask(有返回值)或RecursiveAction
2. 实现compute()方法
3. 调用fork()分解任务
4. 调用join()合并结果
示例:计算1~n的和
class SumTask extends RecursiveTask<Long> {
protected Long compute() {
if (区间足够小) {
return 直接计算;
} else {
SumTask left = new SumTask(前半区间);
left.fork();
SumTask right = new SumTask(后半区间);
return right.compute() + left.join();
}
}
}
2. Disruptor框架
Q11: Disruptor为什么比BlockingQueue快?
答案:
高性能原理:
1. 环形数组结构:
- 预分配内存
- 无GC压力
2. 消除伪共享:
- 缓存行填充(Padding)
3. 无锁设计:
- 序列号控制(Sequence)
- CAS操作
4. 批量处理:
- 一次获取多个事件
关键组件:
1. RingBuffer:数据存储
2. Sequence:生产/消费进度
3. EventProcessor:事件处理器
4. WaitStrategy:等待策略
性能对比:
Disruptor吞吐量可达百万级/秒,延迟在微秒级
六、并发问题排查
1. 死锁诊断
Q12: 如何排查和解决Java死锁问题?
答案:
诊断步骤:
1. jstack <pid>:
- 查找"Found one Java-level deadlock"
- 查看线程堆栈和锁持有情况
2. JConsole/VisualVM:
- 线程监控→检测死锁
3. Arthas:
- thread -b 命令直接定位死锁
预防措施:
1. 锁顺序:
- 全局定义锁获取顺序
2. 超时机制:
- tryLock(timeout)
3. 避免嵌套锁:
- 减少同步代码块嵌套
4. 使用并发容器:
- 如ConcurrentHashMap
解决案例:
// 错误的锁顺序
Thread1: 获取锁A → 尝试获取锁B
Thread2: 获取锁B → 尝试获取锁A
修正方案:统一按A→B顺序获取锁
2. 线程安全设计
Q13: 如何设计线程安全的单例模式?
答案:
1. 饿汉式(线程安全):
class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
2. 双重检查锁(DCL):
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;
}
}
3. 静态内部类:
class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
4. 枚举式(最佳实践):
enum Singleton {
INSTANCE;
public void doSomething() {...}
}
以上内容涵盖了Java多线程的核心知识点,建议结合具体项目经验准备实际案例。对于高级岗位,面试官通常会深入追问实现细节和性能优化方面的实践。