← 返回面试专题导航

⚡ Java 并发编程面试题总览

掌握多线程、锁机制、线程池的核心原理

并发编程是 Java 高级开发的必备技能,也是面试的重点难点。本篇系统梳理并发编程的核心知识。

🎯 难度筛选

📚 本篇覆盖内容

本篇重点围绕并发编程的核心知识,帮助你系统掌握:

  1. 线程基础:线程与进程、线程状态、线程创建方式。
  2. 锁机制:synchronized、ReentrantLock、读写锁、死锁。
  3. 线程池:ThreadPoolExecutor 原理、参数配置、拒绝策略。
  4. 并发工具:volatile、CAS、AQS、CountDownLatch、Semaphore。

一、线程基础

1. 线程和进程的区别? 简单
进程 vs 线程: ┌────────────────────────────┐ │ 进程(Process) │ │ ┌──────┐ ┌──────┐ ┌──────┐│ │ │线程1 │ │线程2 │ │线程3 ││ │ └──────┘ └──────┘ └──────┘│ │ 共享:堆内存、方法区 │ │ 独立:栈、程序计数器 │ └────────────────────────────┘

主要区别:

  • 进程:资源分配的基本单位,拥有独立的内存空间
  • 线程:CPU 调度的基本单位,共享进程的内存空间
  • 开销:进程切换开销大,线程切换开销小
  • 通信:进程间通信复杂(IPC),线程间通信简单(共享内存)
💡 记忆口诀:进程是资源容器,线程是执行单元。
2. 线程有哪几种状态?如何转换? 中等
线程状态转换图: NEW(新建) ↓ start() RUNNABLE(就绪/运行) ↓ ↑ ↓ ↑ notify()/notifyAll() ↓ ↑ WAITING(等待)← wait() ↓ TIMED_WAITING(超时等待)← sleep()/wait(time) ↓ BLOCKED(阻塞)← 等待锁 ↓ TERMINATED(终止)

六种状态:

  • NEW:线程创建但未启动
  • RUNNABLE:就绪或运行中
  • BLOCKED:等待获取锁
  • WAITING:无限期等待(wait、join)
  • TIMED_WAITING:有时限等待(sleep、wait(time))
  • TERMINATED:线程执行完毕
Thread thread = new Thread(() -> { System.out.println("Running"); }); System.out.println(thread.getState()); // NEW thread.start(); System.out.println(thread.getState()); // RUNNABLE
3. 创建线程有哪几种方式? 简单
// 方式1:继承 Thread 类 class MyThread extends Thread { @Override public void run() { System.out.println("Thread running"); } } new MyThread().start(); // 方式2:实现 Runnable 接口(推荐) Runnable task = () -> System.out.println("Runnable running"); new Thread(task).start(); // 方式3:实现 Callable 接口(有返回值) Callable callable = () -> "Callable result"; FutureTask futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); String result = futureTask.get(); // 获取返回值 // 方式4:线程池(推荐) ExecutorService executor = Executors.newFixedThreadPool(5); executor.submit(() -> System.out.println("Pool running")); executor.shutdown();
💡 推荐:优先使用 Runnable/Callable + 线程池,避免直接继承 Thread。
4. sleep() 和 wait() 的区别? 中等
sleep() vs wait(): ┌──────────┬──────────────┬──────────────┐ │ 特性 │ sleep() │ wait() │ ├──────────┼──────────────┼──────────────┤ │ 所属类 │ Thread │ Object │ │ 释放锁 │ 不释放 │ 释放 │ │ 使用场景 │ 暂停执行 │ 线程通信 │ │ 唤醒方式 │ 自动 │ notify() │ │ 同步要求 │ 无 │ 必须在同步块 │ └──────────┴──────────────┴──────────────┘
// sleep:不释放锁 synchronized (lock) { Thread.sleep(1000); // 持有锁,其他线程无法进入 } // wait:释放锁 synchronized (lock) { lock.wait(); // 释放锁,其他线程可以进入 // 被 notify() 唤醒后,重新获取锁 }
⚠️ 注意:wait() 必须在 synchronized 块中使用,否则抛 IllegalMonitorStateException。
5. synchronized 的实现原理?锁升级过程是怎样的? 困难

底层实现:基于 Monitor(监视器锁)

// 字节码层面 monitorenter // 进入同步块 monitorexit // 退出同步块

锁升级过程(JDK 1.6+):

锁升级路径: 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 1. 偏向锁:只有一个线程访问,记录线程ID 2. 轻量级锁:多线程交替访问,CAS 自旋 3. 重量级锁:多线程竞争激烈,阻塞等待

三种使用方式:

// 1. 修饰实例方法:锁是当前实例对象 public synchronized void method() { } // 2. 修饰静态方法:锁是当前类的 Class 对象 public static synchronized void method() { } // 3. 修饰代码块:锁是指定对象 synchronized (lock) { // 临界区代码 }
💡 优化建议:锁的范围越小越好,避免在同步块中执行耗时操作。
6. synchronized 和 ReentrantLock 的区别? 困难
synchronized vs ReentrantLock: ┌──────────┬──────────────┬──────────────┐ │ 特性 │ synchronized │ReentrantLock │ ├──────────┼──────────────┼──────────────┤ │ 实现层面 │ JVM 层面 │ API 层面 │ │ 锁释放 │ 自动 │ 手动 │ │ 可中断 │ 不可中断 │ 可中断 │ │ 公平锁 │ 非公平 │ 可选 │ │ 条件变量 │ 1个 │ 多个 │ │ 性能 │ JDK1.6+相当 │ 相当 │ └──────────┴──────────────┴──────────────┘
// synchronized:自动释放 synchronized (lock) { // 自动释放锁 } // ReentrantLock:手动释放 ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 临界区代码 } finally { lock.unlock(); // 必须在 finally 中释放 } // ReentrantLock 高级功能 // 1. 可中断 lock.lockInterruptibly(); // 2. 尝试获取锁 if (lock.tryLock(1, TimeUnit.SECONDS)) { try { // 获取锁成功 } finally { lock.unlock(); } } // 3. 公平锁 ReentrantLock fairLock = new ReentrantLock(true); // 4. 多个条件变量 Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition();

使用场景:

  • synchronized:简单场景,代码简洁
  • ReentrantLock:需要高级功能(可中断、超时、公平锁、多条件)
7. 什么是死锁?如何避免死锁? 中等

死锁:两个或多个线程互相持有对方需要的锁,导致永久阻塞。

// 死锁示例 Object lock1 = new Object(); Object lock2 = new Object(); // 线程1 new Thread(() -> { synchronized (lock1) { Thread.sleep(100); synchronized (lock2) { // 等待 lock2 System.out.println("Thread 1"); } } }).start(); // 线程2 new Thread(() -> { synchronized (lock2) { Thread.sleep(100); synchronized (lock1) { // 等待 lock1 System.out.println("Thread 2"); } } }).start();

死锁的四个必要条件:

  • 互斥:资源不能被共享
  • 占有并等待:持有资源的同时等待其他资源
  • 不可剥夺:资源不能被强制释放
  • 循环等待:形成环形等待链

避免死锁的方法:

  • 固定加锁顺序:所有线程按相同顺序获取锁
  • 使用超时:tryLock(timeout)
  • 死锁检测:使用 jstack 等工具检测
// 避免死锁:固定加锁顺序 Object lock1 = new Object(); Object lock2 = new Object(); // 所有线程都按 lock1 -> lock2 的顺序加锁 synchronized (lock1) { synchronized (lock2) { // 不会死锁 } }
8. 线程池的核心参数有哪些?如何配置? 困难

ThreadPoolExecutor 七大参数:

public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲线程存活时间 TimeUnit unit, // 时间单位 BlockingQueue workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )
线程池执行流程: 1. 线程数 < corePoolSize → 创建核心线程 2. 线程数 = corePoolSize → 任务入队列 3. 队列满 && 线程数 < maximumPoolSize → 创建非核心线程 4. 线程数 = maximumPoolSize && 队列满 → 执行拒绝策略

四种拒绝策略:

  • AbortPolicy:抛异常(默认)
  • CallerRunsPolicy:调用者线程执行
  • DiscardPolicy:直接丢弃
  • DiscardOldestPolicy:丢弃队列最老的任务
// 自定义线程池(推荐) ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲线程存活60秒 new LinkedBlockingQueue<>(100), // 队列容量100 Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() );

参数配置建议:

  • CPU 密集型:核心线程数 = CPU 核数 + 1
  • IO 密集型:核心线程数 = CPU 核数 × 2
  • 混合型:根据实际情况调整
⚠️ 注意:不推荐使用 Executors 创建线程池,因为默认队列容量是 Integer.MAX_VALUE,可能导致 OOM。
9. volatile 关键字的作用? 中等

两大作用:

  • 保证可见性:一个线程修改后,其他线程立即可见
  • 禁止指令重排序:保证有序性
// 不使用 volatile:可能出现可见性问题 class Task { private boolean flag = false; public void stop() { flag = true; // 线程1修改 } public void run() { while (!flag) { // 线程2可能看不到修改 // 可能死循环 } } } // 使用 volatile:保证可见性 class Task { private volatile boolean flag = false; public void stop() { flag = true; // 线程1修改 } public void run() { while (!flag) { // 线程2立即可见 // 正常退出 } } }

使用场景:

  • 状态标志位(如上面的 flag)
  • 双重检查锁定(DCL)中的单例对象
⚠️ 注意:volatile 不保证原子性!count++ 不是原子操作,需要用 AtomicInteger。
10. 什么是 CAS?有什么问题? 困难

CAS(Compare And Swap):比较并交换,是一种无锁算法。

// CAS 伪代码 boolean compareAndSwap(int expectedValue, int newValue) { if (currentValue == expectedValue) { currentValue = newValue; return true; } return false; } // AtomicInteger 使用 CAS AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); // 原子操作,底层使用 CAS // 手动使用 CAS int oldValue, newValue; do { oldValue = count.get(); newValue = oldValue + 1; } while (!count.compareAndSet(oldValue, newValue));

CAS 的三大问题:

  • ABA 问题:值从 A 变成 B 再变回 A,CAS 认为没变化
    解决:使用 AtomicStampedReference(版本号)
  • 循环时间长开销大:自旋 CAS 如果长时间不成功,会占用大量 CPU
    解决:限制自旋次数,或使用锁
  • 只能保证一个变量的原子性:多个变量需要用锁
    解决:使用 AtomicReference 封装多个变量
💡 应用场景:CAS 广泛应用于 Atomic 类、ConcurrentHashMap、AQS 等并发工具。
11. 什么是 AQS?它的实现原理是什么? 困难

AQS(AbstractQueuedSynchronizer):抽象队列同步器,是 JUC 并发包的基础框架。

AQS 核心结构: ┌─────────────────────────────┐ │ volatile int state(状态) │ │ ┌───────────────────────┐ │ │ │ CLH 队列(双向链表) │ │ │ │ Node1 ← → Node2 ← → Node3│ │ └───────────────────────┘ │ └─────────────────────────────┘

核心思想:

  • 使用 state 表示同步状态(0表示未锁定,1表示锁定)
  • 使用 CLH 队列管理等待线程
  • 使用 CAS 修改 state

基于 AQS 的实现:

  • ReentrantLock:state 表示重入次数
  • Semaphore:state 表示许可证数量
  • CountDownLatch:state 表示计数器
  • ReentrantReadWriteLock:state 高16位表示读锁,低16位表示写锁
// AQS 使用示例:自定义锁 class MyLock { private final Sync sync = new Sync(); static class Sync extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int arg) { return compareAndSetState(0, 1); } @Override protected boolean tryRelease(int arg) { setState(0); return true; } } public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } }
12. ThreadLocal 的原理和使用场景? 中等

ThreadLocal:线程本地变量,每个线程都有自己的副本。

ThreadLocal 结构: Thread └─ ThreadLocalMap ├─ Entry(ThreadLocal1, value1) ├─ Entry(ThreadLocal2, value2) └─ Entry(ThreadLocal3, value3)
// ThreadLocal 使用示例 ThreadLocal threadLocal = new ThreadLocal<>(); // 线程1 new Thread(() -> { threadLocal.set("Thread 1"); System.out.println(threadLocal.get()); // Thread 1 }).start(); // 线程2 new Thread(() -> { threadLocal.set("Thread 2"); System.out.println(threadLocal.get()); // Thread 2 }).start(); // 使用完后要 remove,避免内存泄漏 threadLocal.remove();

使用场景:

  • 数据库连接:每个线程持有自己的连接
  • 用户信息:存储当前登录用户信息
  • SimpleDateFormat:避免线程安全问题
⚠️ 内存泄漏:ThreadLocal 使用完后必须调用 remove(),否则在线程池场景下可能导致内存泄漏。
13. CountDownLatch 和 CyclicBarrier 的区别? 中等
CountDownLatch vs CyclicBarrier: ┌──────────┬──────────────┬──────────────┐ │ 特性 │CountDownLatch│CyclicBarrier │ ├──────────┼──────────────┼──────────────┤ │ 计数方式 │ 递减到0 │ 递增到N │ │ 可重用 │ 否 │ 是 │ │ 使用场景 │ 等待多个任务 │ 多线程协作 │ │ 底层实现 │ AQS │ ReentrantLock│ └──────────┴──────────────┴──────────────┘
// CountDownLatch:等待多个任务完成 CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(() -> { System.out.println("Task done"); latch.countDown(); // 计数减1 }).start(); } latch.await(); // 等待计数归0 System.out.println("All tasks done"); // CyclicBarrier:多线程到达屏障点后一起执行 CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All threads ready"); }); for (int i = 0; i < 3; i++) { new Thread(() -> { System.out.println("Thread ready"); barrier.await(); // 等待其他线程 System.out.println("Thread continue"); }).start(); }

使用场景:

  • CountDownLatch:主线程等待多个子线程完成(如并行计算)
  • CyclicBarrier:多线程互相等待,到达屏障点后一起执行(如多人游戏)
14. Semaphore 的作用和使用场景? 简单

Semaphore(信号量):控制同时访问某个资源的线程数量。

// 限制最多3个线程同时访问 Semaphore semaphore = new Semaphore(3); for (int i = 0; i < 10; i++) { new Thread(() -> { try { semaphore.acquire(); // 获取许可证 System.out.println("Thread " + Thread.currentThread().getName() + " running"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 释放许可证 } }).start(); }

使用场景:

  • 限流:限制同时访问某个资源的线程数
  • 连接池:限制数据库连接数
  • 停车场:限制停车位数量
💡 扩展:Semaphore(1) 可以当作互斥锁使用,但不支持重入。
15. 如何实现一个线程安全的单例模式? 中等
// 方式1:饿汉式(推荐,简单) class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } // 方式2:双重检查锁定(DCL,推荐) class Singleton { private static volatile 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 { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } } // 方式4:枚举(最安全,防反射和序列化) enum Singleton { INSTANCE; public void doSomething() { // 业务方法 } }
⚠️ 注意:DCL 中的 volatile 不能省略,否则可能出现指令重排序问题。

二、进阶知识

16. Java 内存模型(JMM)是什么? 困难

JMM(Java Memory Model):定义了线程和主内存之间的抽象关系,规定了共享变量的访问规则。

JMM 内存模型: ┌─────────────────────────────────────────┐ │ 主内存(Main Memory) │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │变量A│ │变量B│ │变量C│ │ │ └─────┘ └─────┘ └─────┘ │ └─────────────────────────────────────────┘ ↑↓ ↑↓ ↑↓ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 工作内存1 │ │ 工作内存2 │ │ 工作内存3 │ │ (线程1) │ │ (线程2) │ │ (线程3) │ │ 变量A副本 │ │ 变量B副本 │ │ 变量C副本 │ └──────────┘ └──────────┘ └──────────┘

JMM 三大特性:

┌──────────────┬──────────────────────────────────────┐ │ 特性 │ 说明 │ ├──────────────┼──────────────────────────────────────┤ │ 原子性 │ 操作不可分割(synchronized/Lock) │ │ 可见性 │ 修改对其他线程可见(volatile/锁) │ │ 有序性 │ 程序按顺序执行(volatile/锁) │ └──────────────┴──────────────────────────────────────┘

happens-before 原则:

  • 程序顺序规则:同一线程中,前面的操作 happens-before 后面的操作
  • 锁规则:unlock 操作 happens-before 后续的 lock 操作
  • volatile 规则:volatile 写 happens-before 后续的 volatile 读
  • 传递性:A happens-before B,B happens-before C,则 A happens-before C
17. 什么是指令重排序?如何防止? 中等

指令重排序:编译器和 CPU 为了优化性能,可能改变代码执行顺序。

// 原始代码 int a = 1; // 语句1 int b = 2; // 语句2 int c = a + b; // 语句3 // 可能被重排序为 int b = 2; // 语句2 int a = 1; // 语句1 int c = a + b; // 语句3 // DCL 单例中的重排序问题 instance = new Singleton(); // 实际执行可能是: // 1. 分配内存空间 // 2. 将引用指向内存(此时 instance != null) // 3. 初始化对象 // 如果 2 和 3 重排序,其他线程可能拿到未初始化的对象!

防止重排序的方法:

  • volatile:禁止指令重排序
  • synchronized:保证有序性
  • Lock:保证有序性
18. 读写锁 ReentrantReadWriteLock 的原理? 困难

读写锁:读读共享、读写互斥、写写互斥,适用于读多写少场景。

读写锁规则: ┌──────────────┬──────────────┬──────────────┐ │ │ 读锁 │ 写锁 │ ├──────────────┼──────────────┼──────────────┤ │ 读锁 │ ✅ 共享 │ ❌ 互斥 │ │ 写锁 │ ❌ 互斥 │ ❌ 互斥 │ └──────────────┴──────────────┴──────────────┘
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); ReadLock readLock = rwLock.readLock(); WriteLock writeLock = rwLock.writeLock(); // 读操作:多线程可同时读 public String read() { readLock.lock(); try { return data; } finally { readLock.unlock(); } } // 写操作:独占访问 public void write(String newData) { writeLock.lock(); try { data = newData; } finally { writeLock.unlock(); } } // 锁降级:写锁 → 读锁(允许) writeLock.lock(); try { // 修改数据 readLock.lock(); // 获取读锁 } finally { writeLock.unlock(); // 释放写锁,保持读锁 } // 继续持有读锁...
💡 适用场景:缓存系统、配置读取等读多写少的场景。
19. 什么是公平锁和非公平锁? 中等
公平锁 vs 非公平锁: ┌──────────────┬──────────────────┬──────────────────┐ │ 特性 │ 公平锁 │ 非公平锁 │ ├──────────────┼──────────────────┼──────────────────┤ │ 获取顺序 │ 按申请顺序 │ 可插队 │ │ 吞吐量 │ 较低 │ 较高 │ │ 饥饿问题 │ 无 │ 可能 │ │ 默认实现 │ 需显式指定 │ ReentrantLock默认│ └──────────────┴──────────────────┴──────────────────┘
// 公平锁:按申请顺序获取锁 ReentrantLock fairLock = new ReentrantLock(true); // 非公平锁:允许插队(默认) ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁性能更好的原因: // 1. 减少线程切换开销 // 2. 新线程可能直接获取锁,无需唤醒等待线程
💡 选择建议:大多数场景使用非公平锁(性能更好),只有对顺序有严格要求时才用公平锁。
20. 什么是可重入锁?为什么需要可重入? 中等

可重入锁:同一线程可以多次获取同一把锁,不会死锁。

// 可重入锁示例 ReentrantLock lock = new ReentrantLock(); public void outer() { lock.lock(); try { inner(); // 同一线程再次获取锁 } finally { lock.unlock(); } } public void inner() { lock.lock(); // 可重入,不会死锁 try { // 业务逻辑 } finally { lock.unlock(); } } // synchronized 也是可重入的 public synchronized void method1() { method2(); // 可重入 } public synchronized void method2() { // 同一线程已持有锁 }

为什么需要可重入:

  • 避免递归调用时死锁
  • 简化同步代码的编写
  • 支持方法间的调用链
21. Atomic 原子类的原理?有哪些常用的原子类? 困难

原理:基于 CAS(Compare And Swap)+ volatile 实现无锁并发。

常用原子类分类: ┌──────────────────┬──────────────────────────────────┐ │ 类型 │ 原子类 │ ├──────────────────┼──────────────────────────────────┤ │ 基本类型 │ AtomicInteger, AtomicLong, AtomicBoolean │ │ 数组类型 │ AtomicIntegerArray, AtomicLongArray │ │ 引用类型 │ AtomicReference, AtomicStampedReference │ │ 字段更新器 │ AtomicIntegerFieldUpdater 等 │ │ 累加器(JDK8) │ LongAdder, DoubleAdder │ └──────────────────┴──────────────────────────────────┘
// AtomicInteger 常用方法 AtomicInteger count = new AtomicInteger(0); count.get(); // 获取值 count.set(10); // 设置值 count.incrementAndGet(); // ++i count.getAndIncrement(); // i++ count.compareAndSet(10, 20); // CAS 操作 count.addAndGet(5); // 加5并返回 // LongAdder:高并发下性能更好 LongAdder adder = new LongAdder(); adder.increment(); // 递增 adder.add(10); // 加10 long sum = adder.sum(); // 获取总和 // AtomicStampedReference:解决 ABA 问题 AtomicStampedReference ref = new AtomicStampedReference<>(100, 0); int stamp = ref.getStamp(); ref.compareAndSet(100, 200, stamp, stamp + 1);
💡 性能对比:高并发场景下,LongAdder 性能优于 AtomicLong(分段累加,减少竞争)。
22. 线程池的五种状态是什么? 中等
线程池状态转换: RUNNING(运行中) ↓ shutdown() SHUTDOWN(关闭中)──────────────────┐ ↓ 队列空且线程终止 │ ↓ │ shutdownNow() TIDYING(整理中)←──────────────────┘ ↓ terminated() 执行完毕 TERMINATED(已终止) ┌──────────────┬──────────────────────────────────────┐ │ 状态 │ 说明 │ ├──────────────┼──────────────────────────────────────┤ │ RUNNING │ 接受新任务,处理队列任务 │ │ SHUTDOWN │ 不接受新任务,处理队列任务 │ │ STOP │ 不接受新任务,不处理队列,中断执行中 │ │ TIDYING │ 所有任务终止,workerCount=0 │ │ TERMINATED │ terminated() 方法执行完毕 │ └──────────────┴──────────────────────────────────────┘
// 优雅关闭线程池 executor.shutdown(); // 不再接受新任务,等待已提交任务完成 try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // 超时后强制关闭 } } catch (InterruptedException e) { executor.shutdownNow(); }
23. ForkJoinPool 是什么?工作窃取算法是什么? 困难

ForkJoinPool:专为分治算法设计的线程池,适合递归任务。

工作窃取算法(Work-Stealing): ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 线程1队列 │ │ 线程2队列 │ │ 线程3队列 │ │ [任务1] │ │ [任务4] │ │ [空] │ │ [任务2] │ │ [任务5] │ │ │ │ [任务3] │ │ │ │ ← 窃取 │ └─────────────┘ └─────────────┘ └─────────────┘ ↑ 从其他队列 尾部窃取任务
// ForkJoinPool 使用示例:计算 1+2+...+n class SumTask extends RecursiveTask { private long start, end; private static final long THRESHOLD = 1000; public SumTask(long start, long end) { this.start = start; this.end = end; } @Override protected Long compute() { if (end - start <= THRESHOLD) { // 小任务直接计算 long sum = 0; for (long i = start; i <= end; i++) sum += i; return sum; } // 大任务拆分 long mid = (start + end) / 2; SumTask left = new SumTask(start, mid); SumTask right = new SumTask(mid + 1, end); left.fork(); // 异步执行 right.fork(); return left.join() + right.join(); // 合并结果 } } ForkJoinPool pool = new ForkJoinPool(); Long result = pool.invoke(new SumTask(1, 1000000));
💡 应用场景:并行流(parallelStream)底层就是 ForkJoinPool。

🏆 高频面试真题

24. 【阿里】如何排查线上死锁问题? 困难
# 1. jps 查看 Java 进程 ID jps -l # 2. jstack 导出线程堆栈 jstack -l > thread_dump.txt # 3. 搜索死锁信息 grep -A 50 "Found one Java-level deadlock" thread_dump.txt # 4. 或使用 jconsole/jvisualvm 图形化工具
// 代码中检测死锁 ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] deadlockedThreads = bean.findDeadlockedThreads(); if (deadlockedThreads != null) { ThreadInfo[] infos = bean.getThreadInfo(deadlockedThreads); for (ThreadInfo info : infos) { System.out.println("死锁线程: " + info.getThreadName()); } }

排查步骤:

  • 1. 确认系统卡死症状(CPU 低、响应慢)
  • 2. jstack 导出线程堆栈
  • 3. 分析 BLOCKED 状态的线程
  • 4. 找出循环等待的锁
  • 5. 修复代码(固定加锁顺序/超时获取锁)
25. 【字节】如何实现一个高性能的限流器? 困难
常见限流算法: ┌──────────────┬──────────────────────────────────────┐ │ 算法 │ 特点 │ ├──────────────┼──────────────────────────────────────┤ │ 计数器 │ 简单,但有临界问题 │ │ 滑动窗口 │ 解决临界问题,更平滑 │ │ 漏桶 │ 恒定速率处理,削峰填谷 │ │ 令牌桶 │ 允许突发流量,更灵活(推荐) │ └──────────────┴──────────────────────────────────────┘
// 令牌桶算法实现 class TokenBucket { private long capacity; // 桶容量 private long tokens; // 当前令牌数 private long rate; // 令牌生成速率(个/秒) private long lastTime; // 上次获取时间 public TokenBucket(long capacity, long rate) { this.capacity = capacity; this.rate = rate; this.tokens = capacity; this.lastTime = System.currentTimeMillis(); } public synchronized boolean tryAcquire() { long now = System.currentTimeMillis(); // 计算新增令牌 tokens = Math.min(capacity, tokens + (now - lastTime) * rate / 1000); lastTime = now; if (tokens >= 1) { tokens--; return true; } return false; } } // 使用 Guava RateLimiter(推荐) RateLimiter limiter = RateLimiter.create(100); // 100 QPS if (limiter.tryAcquire()) { // 处理请求 }
26. 【腾讯】CompletableFuture 的使用场景和常用方法? 困难

CompletableFuture:JDK8 引入的异步编程工具,支持链式调用和组合操作。

// 1. 创建异步任务 CompletableFuture future = CompletableFuture.supplyAsync(() -> { return "Hello"; }); // 2. 链式处理 future.thenApply(s -> s + " World") // 转换结果 .thenAccept(System.out::println) // 消费结果 .thenRun(() -> System.out.println("Done")); // 执行后续操作 // 3. 组合多个 Future CompletableFuture f1 = CompletableFuture.supplyAsync(() -> "A"); CompletableFuture f2 = CompletableFuture.supplyAsync(() -> "B"); // 等待所有完成 CompletableFuture.allOf(f1, f2).join(); // 等待任一完成 CompletableFuture.anyOf(f1, f2).join(); // 合并两个结果 f1.thenCombine(f2, (a, b) -> a + b); // 4. 异常处理 future.exceptionally(ex -> "Error: " + ex.getMessage()) .handle((result, ex) -> ex != null ? "Error" : result); // 5. 超时控制(JDK9+) future.orTimeout(1, TimeUnit.SECONDS) .completeOnTimeout("Default", 1, TimeUnit.SECONDS);

应用场景:并行调用多个服务、异步任务编排、非阻塞 IO。