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的核心技能,也是面试的重点。掌握这些知识点,不仅能应对面试,更能在实际项目中写出高质量的并发代码。

核心要点回顾:

  1. 线程基础:创建方式、生命周期、状态转换
  2. 同步机制:synchronized锁升级、volatile内存可见性
  3. Lock框架:ReentrantLock vs synchronized、AQS原理
  4. 线程池:参数调优、监控告警、拒绝策略
  5. 并发工具:CountDownLatch、CyclicBarrier、Semaphore

面试建议:

  • 理论结合实践,多举实际项目例子
  • 深入理解底层原理,不要只背概念
  • 关注性能和最佳实践
  • 展现解决实际问题的能力

本章核心要点:

  • ✅ 线程创建和生命周期管理
  • ✅ synchronized锁升级机制
  • ✅ volatile内存可见性原理
  • ✅ Lock接口和AQS实现原理
  • ✅ 线程池参数调优和监控
  • ✅ 并发工具类的实际应用

下一章预告: Spring生态系统 - IOC、AOP、Spring Boot等框架核心原理

#java面试##秋招笔面试记录##面试##java速通##秋招投递攻略#
Java面试圣经 文章被收录于专栏

Java面试圣经

全部评论

相关推荐

评论
1
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务