【Java工程师面试全攻略】Day3:Java并发编程面试精要
一、前言:并发编程的重要性
在多核处理器成为主流的今天,并发编程能力已成为衡量Java工程师水平的重要标准。据统计,超过80%的中高级Java岗位面试都会深入考察并发编程知识。今天我们将系统梳理Java并发编程的核心考点,帮助你从容应对面试挑战。
二、线程基础与生命周期
2.1 线程状态转换图
NEW → RUNNABLE ↔ RUNNING↓ ↑
TERMINATED ← BLOCKED/WATING/TIMED_WAITING
2.2 线程状态详解
状态 | 触发条件 | 特点 |
---|---|---|
NEW | 刚创建未start() | 未启动 |
RUNNABLE | 调用start() | 可运行状态 |
BLOCKED | 等待监视器锁 | 同步阻塞 |
WAITING | wait()/join() | 无限期等待 |
TIMED_WAITING | sleep(n)/wait(n) | 超时等待 |
TERMINATED | 执行完毕 | 线程结束 |
2.3 创建线程的三种方式
// 1. 继承Thread类
class MyThread extends Thread {public void run() {System.out.println("Thread running");}
}// 2. 实现Runnable接口
class MyRunnable implements Runnable {public void run() {System.out.println("Runnable running");}
}// 3. 实现Callable接口(可获取返回值)
class MyCallable implements Callable<String> {public String call() throws Exception {return "Callable result";}
}// 使用示例
public static void main(String[] args) throws Exception {new MyThread().start();new Thread(new MyRunnable()).start();FutureTask<String> futureTask = new FutureTask<>(new MyCallable());new Thread(futureTask).start();System.out.println(futureTask.get());
}
三、线程同步与锁机制
3.1 synchronized实现原理
对象内存布局:
| Mark Word (32/64bit) | Class Pointer | Instance Data | Padding |
锁升级过程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
3.2 ReentrantLock vs synchronized
特性 | ReentrantLock | synchronized |
---|---|---|
实现方式 | API层面 | JVM层面 |
锁获取 | 可尝试获取tryLock() | 阻塞获取 |
公平性 | 可配置公平/非公平 | 非公平 |
条件变量 | 支持多个Condition | 单一wait/notify |
中断响应 | lockInterruptibly() | 不支持 |
性能 | 高竞争下更优 | 低竞争下更优 |
3.3 volatile关键字
内存语义:
- 可见性:写操作立即刷新到主内存
- 禁止指令重排序
适用场景:
- 状态标志位
- 双重检查锁定(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;}
}
四、并发工具类
4.1 AQS(AbstractQueuedSynchronizer)
核心结构:
- state:同步状态
- CLH队列:等待队列
基于AQS的实现类:
- ReentrantLock
- CountDownLatch
- Semaphore
- ReentrantReadWriteLock
4.2 CountDownLatch使用示例
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(3);new Thread(() -> {System.out.println("Task 1 done");latch.countDown();}).start();new Thread(() -> {System.out.println("Task 2 done");latch.countDown();}).start();new Thread(() -> {System.out.println("Task 3 done");latch.countDown();}).start();latch.await();System.out.println("All tasks completed");}
}
4.3 ThreadLocal原理与内存泄漏
实现原理:
每个Thread维护一个ThreadLocalMap,key为弱引用的ThreadLocal实例
public class ThreadLocal<T> {public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}// 使用后必须remove()避免内存泄漏public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}
}
五、线程池深度解析
5.1 ThreadPoolExecutor核心参数
public ThreadPoolExecutor(int corePoolSize, // 核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 空闲线程存活时间TimeUnit unit, // 时间单位BlockingQueue<Runnable> workQueue, // 工作队列ThreadFactory threadFactory, // 线程工厂RejectedExecutionHandler handler // 拒绝策略
)
5.2 工作流程
- 提交任务
- 核心线程未满 → 创建新线程
- 核心线程已满 → 入队
- 队列已满且线程未达最大值 → 创建非核心线程
- 线程已达最大值 → 执行拒绝策略
5.3 四种拒绝策略
策略 | 实现类 | 行为 |
---|---|---|
直接拒绝 | AbortPolicy | 抛出RejectedExecutionException |
调用者运行 | CallerRunsPolicy | 由提交线程执行任务 |
丢弃最老 | DiscardOldestPolicy | 丢弃队列头任务并重试 |
静默丢弃 | DiscardPolicy | 直接丢弃不通知 |
六、高频面试题解析
6.1 问题1:synchronized和ReentrantLock的区别?
参考答案:
- 实现层面:synchronized是JVM层面,ReentrantLock是JDK代码
- 功能特性:
- ReentrantLock支持公平锁、可中断、超时等待
- synchronized使用更简单
- 性能:JDK6优化后性能接近,高竞争下ReentrantLock更优
- 锁释放:synchronized自动释放,ReentrantLock需手动unlock()
6.2 问题2:线程池队列满时的处理策略?
解决方案:
- 增大队列容量(如使用无界队列,风险:可能OOM)
- 增加最大线程数
- 优化任务处理时间
- 选择合适的拒绝策略:
- 重要任务:CallerRunsPolicy
- 可丢弃任务:DiscardOldestPolicy
七、实战编码题
题目:实现生产者-消费者模式
public class ProducerConsumer {private static BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);static class Producer implements Runnable {public void run() {try {for (int i = 0; i < 100; i++) {queue.put(i);System.out.println("Produced: " + i);Thread.sleep(100);}} catch (InterruptedException e) {e.printStackTrace();}}}static class Consumer implements Runnable {public void run() {try {while (true) {Integer item = queue.take();System.out.println("Consumed: " + item);Thread.sleep(200);}} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {new Thread(new Producer()).start();new Thread(new Consumer()).start();}
}
八、明日预告
明天我们将探讨《JVM原理与性能调优》,内容包括:
- JVM内存模型详解
- 垃圾回收算法与回收器对比
- 常见的OOM原因及解决方案
- JVM性能调优实战技巧
- 常用JVM参数解析
九、昨日思考题答案
问题:ConcurrentHashMap在JDK7和JDK8中的实现区别?
答案:
- JDK7使用分段锁(Segment),默认16段
- JDK8改用CAS+synchronized,锁粒度更细(桶级别)
- JDK8引入红黑树优化哈希冲突
- JDK8的size()实现更精确(使用CounterCell)
- JDK8的迭代器弱一致性改进
欢迎在评论区分享你的并发编程实战经验,我们明天见!