Skip to content

并发面试题

面试问到并发,逃不开这些问题。

本章节帮你梳理高频面试题,理解背后的原理,而不仅仅是背答案。

线程基础

Q1: 进程和线程的区别?

对比项进程线程
资源拥有独立的内存空间共享进程资源
开销大(进程创建、切换)小(线程创建、切换)
通信IPC(管道、消息队列等)直接读写共享内存
独立性强进程间隔离线程间共享

简单说:进程是资源分配的单位,线程是 CPU 调度的单位

Q2: 创建线程有几种方式?

java
// 方式1:继承 Thread
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread 方式");
    }
}
new MyThread().start();

// 方式2:实现 Runnable
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable 方式");
    }
}
new Thread(new MyRunnable()).start();

// 方式3:实现 Callable + FutureTask
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() {
        return 42;
    }
}
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
System.out.println(task.get());

// 方式4:线程池
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("线程池方式"));

本质上只有一种:创建 Thread 对象。Runnable 和 Callable 只是任务载体。

Q3: sleep() 和 wait() 的区别?

对比项sleep()wait()
所属Thread 类Object 类
锁行为不释放锁释放锁
唤醒方式超时/中断notify() / notifyAll()
使用场景暂停执行线程通信
调用位置任意位置必须在 synchronized 中
java
// sleep 不释放锁
synchronized (lock) {
    Thread.sleep(1000);  // 锁还在手里
}

// wait 释放锁
synchronized (lock) {
    while (conditionNotMet) {
        lock.wait();  // 锁放开
    }
}

synchronized 和 Lock

Q4: synchronized 和 Lock 的区别?

对比项synchronizedLock
获取方式自动获取/释放手动获取/释放
灵活性高(tryLock、读写锁)
超时等待不支持支持
中断等待不支持支持
公平锁非公平可配置
条件变量内置(this)多个 Condition
java
// synchronized:自动释放
synchronized (lock) {
    // 操作
} // 自动释放

// Lock:手动释放
lock.lock();
try {
    // 操作
} finally {
    lock.unlock();  // 必须手动释放
}

// Lock 高级特性
ReentrantLock lock = new ReentrantLock();

// tryLock:带超时
if (lock.tryLock(5, TimeUnit.SECONDS)) {
    try {
        // 操作
    } finally {
        lock.unlock();
    }
}

// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);

// 读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();  // 读锁,多线程可并发
rwLock.writeLock().lock(); // 写锁,排他

Q5: synchronized 底层原理?

java
synchronized (obj) {
    // 代码
}

字节码层面:

monitorenter   // 获取锁
// 代码
monitorexit    // 释放锁

JVM 层面:

  • 无锁:对象头标志位为无锁状态
  • 偏向锁:第一次获取时记录线程 ID,后续无竞争无需同步
  • 轻量级锁:自旋CAS获取
  • 重量级锁:阻塞等待

volatile

Q6: volatile 的作用?

两个作用,缺一不可:

  1. 保证可见性:写操作立即刷新到主内存,读操作立即从主内存读取
  2. 防止指令重排序:通过内存屏障阻止编译器和 CPU 重排序

不保证原子性i++ 这种复合操作,volatile 保护不了。

java
// ❌ volatile 不能保证原子性
private volatile int counter = 0;
counter++;  // 三个步骤,volatile 只保证读写

// ✅ 原子类才能保证
private final AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();

线程池

Q7: 线程池参数?

java
ThreadPoolExecutor(
    int corePoolSize,          // 核心线程数
    int maximumPoolSize,        // 最大线程数
    long keepAliveTime,         // 空闲存活时间
    TimeUnit unit,              // 时间单位
    BlockingQueue<Runnable> workQueue,  // 任务队列
    ThreadFactory threadFactory, // 线程工厂
    RejectedExecutionHandler handler  // 拒绝策略
)

Q8: 线程池工作流程?

任务提交

    ├─► 核心线程未满 ──► 创建核心线程执行

    ├─► 核心线程已满 ──► 任务入队

    ├─► 队列已满 ──► 创建临时线程执行

    └─► 线程数达上限 ──► 执行拒绝策略

Q9: Executors 创建的线程池有什么问题?

java
// ❌ FixedThreadPool:队列无界,可能 OOM
Executors.newFixedThreadPool(10);
// 内部:new LinkedBlockingQueue<>() 无界

// ❌ CachedThreadPool:线程数无界,可能 OOM
Executors.newCachedThreadPool();
// 内部:maximumPoolSize = Integer.MAX_VALUE

// ❌ SingleThreadExecutor:队列无界,可能 OOM
Executors.newSingleThreadExecutor();

// ✅ 正确做法:显式指定队列大小
new ThreadPoolExecutor(
    10, 20, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),  // 有界队列
    new ThreadPoolExecutor.AbortPolicy()
);

CAS 和原子类

Q10: CAS 原理?

Compare-And-Swap:
┌─────────────────────────────────┐
│  1. 读取内存值 V                 │
│  2. 比较 V 是否等于期望值 A      │
│  3. 相等则写入新值 B,返回 true  │
│  4. 不等则什么都不做,返回 false │
└─────────────────────────────────┘

ABA 问题:值从 A 变成 B 又变回 A,CAS 会认为没变
解决:AtomicStampedReference(带版本号)

Q11: ConcurrentHashMap 1.7 和 1.8 的区别?

版本实现并发控制
JDK 7Segment + 数组 + 链表分段锁(16 个 Segment)
JDK 8CAS + synchronized + 红黑树锁细化(只锁单个桶)

JDK 8 去掉了 Segment,性能大幅提升。

线程安全集合

Q12: HashMap 线程安全吗?

不安全。并发问题:

  1. 数据覆盖
  2. JDK 7 扩容死循环(JDK 8 已修复)

安全替代方案:

java
// 方案1:ConcurrentHashMap
ConcurrentHashMap<String, Integer> map1 = new ConcurrentHashMap<>();

// 方案2:Collections.synchronizedMap
Map<String, Integer> map2 = Collections.synchronizedMap(new HashMap<>());

// 方案3:Hashtable(不推荐,性能差)
Hashtable<String, Integer> map3 = new Hashtable<>();

Q13: CopyOnWriteArrayList 和 synchronizedList 区别?

特性CopyOnWriteArrayListsynchronizedList
读操作无锁,最快有锁
写操作复制整个数组有锁
迭代器弱一致,不抛异常快速失败
适用读多写少写多读少

面试回答技巧

技巧一:原理优先

不要只背结论,要理解为什么。

问:synchronized 和 ReentrantLock 哪个快?
答:在低竞争场景下两者性能接近,
    但 synchronized 有偏向锁优化,第一次获取几乎无开销。
    在高竞争场景下,ReentrantLock 的 tryLock 特性更有优势。

技巧二:场景驱动

不要孤立回答,结合使用场景。

问:什么时候用 volatile?
答:当只有一个线程写、多个线程读的状态标志时用 volatile。
    比如 shutdown flag、DCL 单例模式中的实例引用。
    如果需要复合操作,就得用原子类或锁。

技巧三:深度延伸

回答完基本问题,可以主动延伸。

问:线程池的核心参数?
答:7 个参数。重点是 corePoolSize 和 maximumPoolSize 的关系,
    以及 workQueue 的选择。
    实际上,我更推荐显式创建 ThreadPoolExecutor,
    而不是用 Executors 工具类,因为后者容易导致 OOM。

必背口诀

线程安全:
  单一变量 → Atomic*
  复合操作 → synchronized / Lock
  线程隔离 → ThreadLocal
  高并发 Map → ConcurrentHashMap
  读多写少 → CopyOnWrite

JMM 三要素:
  原子性 → synchronized / Lock / Atomic*
  可见性 → volatile / synchronized
  有序性 → volatile / synchronized / happens-before

线程池:
  CPU 密集 → 核心数
  I/O 密集 → 核心数 × (1 + 等待/计算)
  队列必须有界,防止 OOM

要点回顾

  • 线程基础:进程 vs 线程,sleep vs wait
  • 同步机制:synchronized vs Lock,各有适用场景
  • volatile:保证可见性和有序性,不保证原子性
  • 线程池:核心参数 + 工作流程 + 避免 Executors
  • 并发集合:根据读写比选择合适的集合
  • CAS:无锁算法,了解 ABA 问题和解决方案

相关链接

基于 VitePress 构建