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)
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经
查看21道真题和解析

