Java面试秋招之多线程与并发编程
第5章 多线程与并发编程
面试重要程度:⭐⭐⭐⭐⭐
常见提问方式:线程池参数、synchronized vs Lock、volatile原理
预计阅读时间:40分钟
开场白
兄弟,并发编程绝对是Java面试的核心!这块知识不仅考察你的理论基础,更能看出你在实际项目中处理并发问题的能力。我见过太多人在这里翻车,明明其他方面都不错,就是并发这块答不好。
今天我们就把Java并发的核心知识点彻底搞清楚,让你在面试中展现出扎实的并发编程功底。
🧵 5.1 线程基础与生命周期
线程创建方式
面试必问:
面试官:"Java中创建线程有几种方式?各有什么优缺点?"
四种创建方式:
// 1. 继承Thread类 class MyThread extends Thread { @Override public void run() { System.out.println("Thread: " + Thread.currentThread().getName()); } } // 2. 实现Runnable接口(推荐) class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable: " + Thread.currentThread().getName()); } } // 3. 实现Callable接口(有返回值) class MyCallable implements Callable<String> { @Override public String call() throws Exception { return "Callable result: " + Thread.currentThread().getName(); } } // 4. 使用线程池(最佳实践) public class ThreadCreationDemo { public static void main(String[] args) throws Exception { // 方式1:继承Thread new MyThread().start(); // 方式2:实现Runnable new Thread(new MyRunnable()).start(); // 方式3:Callable + FutureTask FutureTask<String> futureTask = new FutureTask<>(new MyCallable()); new Thread(futureTask).start(); System.out.println(futureTask.get()); // 获取返回值 // 方式4:线程池 ExecutorService executor = Executors.newFixedThreadPool(2); executor.submit(new MyRunnable()); executor.submit(new MyCallable()); executor.shutdown(); } }
优缺点对比:
继承Thread: ✅ 简单直接 ❌ 单继承限制,耦合度高 实现Runnable: ✅ 避免单继承限制,代码复用性好 ✅ 任务和线程分离 ❌ 无返回值 实现Callable: ✅ 有返回值,可以抛出异常 ❌ 使用相对复杂 线程池: ✅ 资源复用,性能好,便于管理 ✅ 生产环境首选 ❌ 配置相对复杂
线程生命周期
面试重点:
面试官:"说说线程的生命周期,各个状态之间如何转换?"
线程状态图:
public enum State { NEW, // 新建 RUNNABLE, // 可运行(就绪+运行) BLOCKED, // 阻塞 WAITING, // 等待 TIMED_WAITING,// 超时等待 TERMINATED; // 终止 }
状态转换示例:
public class ThreadStateDemo { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread thread = new Thread(() -> { synchronized (lock) { try { System.out.println("线程开始等待"); lock.wait(); // WAITING状态 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); System.out.println("创建后状态: " + thread.getState()); // NEW thread.start(); Thread.sleep(100); System.out.println("启动后状态: " + thread.getState()); // WAITING synchronized (lock) { lock.notify(); // 唤醒线程 } thread.join(); System.out.println("结束后状态: " + thread.getState()); // TERMINATED } }
🔒 5.2 synchronized关键字深入
锁升级过程
面试高频:
面试官:"synchronized的锁升级过程是怎样的?"
锁升级路径:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
详细分析:
public class SynchronizedDemo { private int count = 0; // 1. 偏向锁:只有一个线程访问 public synchronized void increment() { count++; // 第一次访问,获得偏向锁 } // 2. 轻量级锁:少量线程竞争,自旋等待 public void lightweightLock() { synchronized (this) { // 多个线程竞争,升级为轻量级锁 // 通过CAS和自旋获取锁 count++; } } // 3. 重量级锁:竞争激烈,线程阻塞 public void heavyweightLock() { synchronized (this) { try { // 竞争激烈时,升级为重量级锁 // 线程进入阻塞状态,由操作系统调度 Thread.sleep(100); count++; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
锁升级的触发条件:
// 偏向锁 → 轻量级锁 // 当有第二个线程尝试获取偏向锁时 // 轻量级锁 → 重量级锁 // 当自旋次数超过阈值(默认10次)或自旋线程数超过CPU核数一半时
synchronized的实现原理
字节码层面:
public class SyncBytecode { public synchronized void method1() { // 方法级别的synchronized } public void method2() { synchronized (this) { // 代码块级别的synchronized } } } // 编译后的字节码: // method1: ACC_SYNCHRONIZED标志 // method2: monitorenter和monitorexit指令
JVM层面实现:
// 对象头结构(64位JVM) // Mark Word (64 bits) + Class Pointer (32 bits) + Array Length (32 bits, 数组才有) // Mark Word在不同锁状态下的存储内容: // 无锁:hashcode(31) + age(4) + biased_lock(1) + lock(2) // 偏向锁:thread_id(54) + epoch(2) + age(4) + biased_lock(1) + lock(2) // 轻量级锁:ptr_to_lock_record(62) + lock(2) // 重量级锁:ptr_to_heavyweight_monitor(62) + lock(2)
⚡ 5.3 volatile与内存可见性
volatile的作用机制
面试重点:
面试官:"volatile关键字的作用是什么?底层是如何实现的?"
核心作用:
public class VolatileDemo { private volatile boolean flag = false; private int count = 0; // 线程1:写操作 public void writer() { count = 42; // 1. 普通写 flag = true; // 2. volatile写 } // 线程2:读操作 public void reader() { if (flag) { // 3. volatile读 int value = count; // 4. 普通读,能看到count=42 } } }
happens-before规则:
1. 程序顺序规则:单线程内,前面的操作happens-before后面的操作 2. volatile规则:volatile写happens-before volatile读 3. 传递性:A happens-before B,B happens-before C,则A happens-before C 因此:count=42 happens-before flag=true happens-before flag读取 happens-before count读取
内存屏障实现
底层实现:
// volatile写操作的内存屏障 StoreStore屏障 volatile写操作 StoreLoad屏障 // volatile读操作的内存屏障 volatile读操作 LoadLoad屏障 LoadStore屏障
实际应用场景:
// 1. 状态标志 public class StatusFlag { private volatile boolean shutdown = false; public void shutdown() { shutdown = true; // 写线程 } public void work() { while (!shutdown) { // 读线程,能立即看到变化 // 执行工作 } } } // 2. 双重检查锁定(DCL) public class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // volatile防止指令重排 } } } return instance; } }
🔐 5.4 Lock接口与AQS原理
ReentrantLock vs synchronized
面试对比:
面试官:"ReentrantLock和synchronized有什么区别?什么时候用哪个?"
功能对比:
public class LockComparison { private final ReentrantLock lock = new ReentrantLock(); private int count = 0; // synchronized方式 public synchronized void syncMethod() { count++; } // ReentrantLock方式 public void lockMethod() { lock.lock(); try { count++; } finally { lock.unlock(); // 必须在finally中释放 } } // ReentrantLock的高级功能 public void advancedFeatures() throws InterruptedException { // 1. 尝试获取锁 if (lock.tryLock()) { try { // 获取到锁的逻辑 } finally { lock.unlock(); } } // 2. 超时获取锁 if (lock.tryLock(1, TimeUnit.SECONDS)) { try { // 1秒内获取到锁的逻辑 } finally { lock.unlock(); } } // 3. 可中断的锁获取 try { lock.lockInterruptibly(); // 可以被interrupt()中断 } finally { lock.unlock(); } } }
选择建议:
使用synchronized的场景: ✅ 简单的同步需求 ✅ JVM自动管理,不会忘记释放 ✅ 性能在JDK 6+已经很好 使用ReentrantLock的场景: ✅ 需要高级功能(tryLock、超时、中断) ✅ 需要公平锁 ✅ 需要条件变量(Condition) ✅ 需要更灵活的锁控制
AQS(AbstractQueuedSynchronizer)原理
核心概念:
// AQS的核心结构 public abstract class AbstractQueuedSynchronizer { // 同步状态 private volatile int state; // 等待队列的头节点 private transient volatile Node head; // 等待队列的尾节点 private transient volatile Node tail; // 队列节点 static final class Node { volatile Node prev; volatile Node next; volatile Thread thread; volatile int waitStatus; } }
ReentrantLock基于AQS的实现:
public class ReentrantLock implements Lock { private final Sync sync; // 非公平锁实现 static final class NonfairSync extends Sync { final void lock() { // 1. 先尝试CAS获取锁 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // 2. 失败则进入AQS队列 } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } // 公平锁实现 static final class FairSync extends Sync { protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 公平锁:检查队列中是否有等待线程 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 重入锁逻辑 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } }
🏊 5.5 线程池核心参数调优
ThreadPoolExecutor详解
面试重点:
面试官:"线程池的核心参数有哪些?如何合理设置?"
七大核心参数:
public ThreadPoolExecutor( int corePoolSize, // 1. 核心线程数 int maximumPoolSize, // 2. 最大线程数 long keepAliveTime, // 3. 空闲线程存活时间 TimeUnit unit, // 4. 时间单位 BlockingQueue<Runnable> workQueue, // 5. 工作队列 ThreadFactory threadFactory, // 6. 线程工厂 RejectedExecutionHandler handler // 7. 拒绝策略 ) { // 构造器实现 }
参数设置原则:
public class ThreadPoolConfig { // CPU密集型任务 public static ExecutorService createCpuIntensivePool() { int coreSize = Runtime.getRuntime().availableProcessors(); return new ThreadPoolExecutor( coreSize, // 核心线程数 = CPU核数 coreSize, // 最大线程数 = CPU核数 0L, TimeUnit.MILLISECONDS, // 不需要额外线程 new LinkedBlockingQueue<>(), // 无界队列 new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "cpu-pool-" + threadNumber.getAndIncrement()); t.setDaemon(false); return t; } }, new ThreadPoolExecutor.CallerRunsPolicy() // 调用者执行策略 ); } // IO密集型任务 public static ExecutorService createIoIntensivePool() { int coreSize = Runtime.getRuntime().availableProcessors() * 2; return new ThreadPoolExecutor( coreSize, // 核心线程数 = CPU核数 * 2 coreSize * 2, // 最大线程数 = CPU核数 * 4 60L, TimeUnit.SECONDS, // 空闲线程60秒后回收 new ArrayBlockingQueue<>(100), // 有界队列,防止OOM r -> new Thread(r, "io-pool-" + System.currentTimeMillis()), new ThreadPoolExecutor.AbortPolicy() // 抛异常策略 ); } }
工作队列选择
队列类型对比:
// 1. ArrayBlockingQueue - 有界队列 BlockingQueue<Runnable> queue1 = new ArrayBlockingQueue<>(100); // 特点:固定容量,防止OOM,但可能触发拒绝策略 // 2. LinkedBlockingQueue - 无界队列(默认Integer.MAX_VALUE) BlockingQueue<Runnable> queue2 = new LinkedBlockingQueue<>(); // 特点:容量大,很少触发拒绝策略,但可能OOM // 3. SynchronousQueue - 直接交换 BlockingQueue<Runnable> queue3 = new SynchronousQueue<>(); // 特点:不存储任务,直接交给线程执行 // 4. PriorityBlockingQueue - 优先级队列 BlockingQueue<Runnable> queue4 = new PriorityBlockingQueue<>(); // 特点:按优先级执行任务
拒绝策略
四种内置策略:
// 1. AbortPolicy - 抛异常(默认) new ThreadPoolExecutor.AbortPolicy() // 2. CallerRunsPolicy - 调用者执行 new ThreadPoolExecutor.CallerRunsPolicy() // 3. DiscardPolicy - 静默丢弃 new ThreadPoolExecutor.DiscardPolicy() // 4. DiscardOldestPolicy - 丢弃最老的任务 new ThreadPoolExecutor.DiscardOldestPolicy() // 5. 自定义策略 RejectedExecutionHandler customHandler = (r, executor) -> { // 记录日志 logger.warn("Task rejected: {}", r.toString()); // 可以选择重试、存储到数据库等 };
线程池监控
监控指标:
public class ThreadPoolMonitor { private final ThreadPoolExecutor executor; public ThreadPoolMonitor(ThreadPoolExecutor executor) { this.executor = executor; } public void printStats() { System.out.println("=== 线程池状态 ==="); System.out.println("核心线程数: " + executor.getCorePoolSize()); System.out.println("最大线程数: " + executor.getMaximumPoolSize()); System.out.println("当前线程数: " + executor.getPoolSize()); System.out.println("活跃线程数: " + executor.getActiveCount()); System.out.println("队列任务数: " + executor.getQueue().size()); System.out.println("已完成任务数: " + executor.getCompletedTaskCount()); System.out.println("总任务数: " + executor.getTaskCount()); } // 定期监控 public void startMonitoring() { ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1); monitor.scheduleAtFixedRate(this::printStats, 0, 30, TimeUnit.SECONDS); } }
🛠️ 5.6 并发工具类实战
CountDownLatch
使用场景:等待多个线程完成
public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { int threadCount = 3; CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { // 模拟工作 Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " 完成工作"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { latch.countDown(); // 计数器减1 } }, "Worker-" + i).start(); } latch.await(); // 等待所有线程完成 System.out.println("所有工作完成,开始汇总"); } }
CyclicBarrier
使用场景:多线程协同工作
public class CyclicBarrierDemo { public static void main(String[] args) { int threadCount = 3; CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> { System.out.println("所有线程都到达屏障,开始下一阶段"); }); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + " 到达屏障"); barrier.await(); // 等待其他线程 System.out.println(Thread.currentThread().getName() + " 继续执行"); } catch (Exception e) { e.printStackTrace(); } }, "Thread-" + i).start(); } } }
Semaphore
使用场景:限制并发数量
public class SemaphoreDemo { private static final Semaphore semaphore = new Semaphore(2); // 最多2个线程 public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(() -> { try { semaphore.acquire(); // 获取许可 System.out.println(Thread.currentThread().getName() + " 获得许可"); Thread.sleep(2000); // 模拟工作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { System.out.println(Thread.currentThread().getName() + " 释放许可"); semaphore.release(); // 释放许可 } }, "Thread-" + i).start(); } } }
💡 美团真题:高并发场景下的线程池设计
面试场景:
面试官:"设计一个处理订单的线程池,要求: 1. 订单处理是IO密集型任务 2. 高峰期QPS可达10万 3. 要保证系统稳定性 4. 需要监控和告警"
设计方案:
@Component public class OrderProcessThreadPool { private static final Logger logger = LoggerFactory.getLogger(OrderProcessThreadPool.class); private final ThreadPoolExecutor orderExecutor; private final ScheduledExecutorService monitorExecutor; public OrderProcessThreadPool() { // 根据机器配置动态计算 int coreSize = Runtime.getRuntime().availableProcessors() * 2; int maxSize = coreSize * 2; this.orderExecutor = new ThreadPoolExecutor( coreSize, maxSize, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), // 有界队列防止OOM new ThreadFactory() { private final AtomicInteger counter = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "order-pool-" + counter.incrementAndGet()); t.setDaemon(false); t.setUncaughtExceptionHandler((thread, ex) -> { logger.error("Thread {} threw exception", thread.getName(), ex); }); return t; } }, new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 自定义拒绝策略:记录日志 + 告警 logger.error("Order task rejected, queue size: {}, active threads: {}", executor.getQueue().size(), executor.getActiveCount()); // 发送告警 sendAlert("线程池任务被拒绝"); // 尝试放入备用队列或降级处理 handleRejectedOrder(r); } } ); // 启动监控 this.monitorExecutor = Executors.newScheduledThreadPool(1); startMonitoring(); } public void processOrder(Order order) { orderExecutor.submit(() -> { try { // 订单处理逻辑 doProcessOrder(order); } catch (Exception e) { logger.error("Process order failed: {}", order.getId(), e); // 失败处理逻辑 } }); } private void startMonitoring() { monitorExecutor.scheduleAtFixedRate(() -> { int queueSize = orderExecutor.getQueue().size(); int activeCount = orderExecutor.getActiveCount(); long completedTaskCount = orderExecutor.getCompletedTaskCount(); // 记录指标 logger.info("ThreadPool stats - Queue: {}, Active: {}, Completed: {}", queueSize, activeCount, completedTaskCount); // 告警检查 if (queueSize > 800) { // 队列使用率80% sendAlert("线程池队列使用率过高: " + queueSize); } if (activeCount == orderExecutor.getMaximumPoolSize()) { sendAlert("线程池已满负荷运行"); } }, 0, 30, TimeUnit.SECONDS); } private void handleRejectedOrder(Runnable r) { // 降级处理:存入数据库,后续异步处理 // 或者使用备用线程池 } private void sendAlert(String message) { // 发送钉钉/微信告警 logger.warn("ALERT: {}", message); } private void doProcessOrder(Order order) { // 具体的订单处理逻辑 } @PreDestroy public void shutdown() { orderExecutor.shutdown(); monitorExecutor.shutdown(); try { if (!orderExecutor.awaitTermination(60, TimeUnit.SECONDS)) { orderExecutor.shutdownNow(); } } catch (InterruptedException e) { orderExecutor.shutdownNow(); Thread.currentThread().interrupt(); } } }
总结
并发编程是Java的核心技能,也是面试的重点。掌握这些知识点,不仅能应对面试,更能在实际项目中写出高质量的并发代码。
核心要点回顾:
- 线程基础:创建方式、生命周期、状态转换
- 同步机制:synchronized锁升级、volatile内存可见性
- Lock框架:ReentrantLock vs synchronized、AQS原理
- 线程池:参数调优、监控告警、拒绝策略
- 并发工具:CountDownLatch、CyclicBarrier、Semaphore
面试建议:
- 理论结合实践,多举实际项目例子
- 深入理解底层原理,不要只背概念
- 关注性能和最佳实践
- 展现解决实际问题的能力
本章核心要点:
- ✅ 线程创建和生命周期管理
- ✅ synchronized锁升级机制
- ✅ volatile内存可见性原理
- ✅ Lock接口和AQS实现原理
- ✅ 线程池参数调优和监控
- ✅ 并发工具类的实际应用
下一章预告: Spring生态系统 - IOC、AOP、Spring Boot等框架核心原理
#java面试##秋招笔面试记录##面试##java速通##秋招投递攻略#Java面试圣经 文章被收录于专栏
Java面试圣经