Skip to content

并发面试题:线程安全不是背出来的

面试时问并发,很多人能把 synchronized 和 Lock 的区别背出来,但被追问:你在实际项目里遇到过线程安全问题吗?怎么排查的? 就答不上来了。

原理要学,实践更重要。

synchronized vs Lock

对比项synchronizedLock
语法关键字,自动释放接口,需手动释放
释放代码块结束自动释放必须 finally
等待不可中断可通过 lock.lockInterruptibly() 中断
公平非公平可公平可非公平
多条件不能Condition 支持多个等待队列
java
// synchronized:自动释放
public synchronized void method() {
    // ...
} // 方法结束自动释放锁

// Lock:手动释放
private final Lock lock = new ReentrantLock();
public void method() {
    lock.lock();
    try {
        // ...
    } finally {
        lock.unlock(); // 必须放在 finally 里
    }
}

volatile:不保证原子性

volatile 有两个作用:可见性禁止指令重排序,但不保证原子性

java
// ❌ volatile 不能保证 ++ 操作的原子性
private volatile int count = 0;

public void increment() {
    count++; // 三个操作:读、改、写,不是原子的
}

// ✅ 真正安全的方式
private volatile int count = 0;
private final Lock lock = new ReentrantLock();

public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

// ✅ 或用 AtomicInteger
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet();
}

生产者消费者:经典问题

java
public class ProducerConsumerDemo {

    static BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);

    static class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    queue.put(i); // 队列满则阻塞
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    static class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Integer value = queue.take(); // 队列空则阻塞
                    System.out.println("Consumed: " + value);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }
}

线程池使用

java
// 固定大小线程池:适合 CPU 密集型
ExecutorService executor = Executors.newFixedThreadPool(10);

// 缓存线程池:适合 IO 密集型
ExecutorService cachedExecutor = Executors.newCachedThreadPool();

// 阿里规范推荐:自定义 ThreadPoolExecutor
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4,                      // corePoolSize
    8,                      // maximumPoolSize
    60L, TimeUnit.SECONDS,  // keepAliveTime
    new LinkedBlockingQueue&lt;&gt;(100), // 队列大小
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

常见追问

Q: synchronized 锁的是什么?

普通方法锁的是 this(即当前对象),静态方法锁的是 Class 对象。

Q: 死锁怎么排查?

bash
# 1. 先找到 Java 进程
jps -l

# 2. 查看线程堆栈
jstack <pid>
# 搜索 "Deadlock" 关键字

Q: 怎么避免死锁?

四个条件破坏一个即可:

  1. 互斥 → 无法破坏
  2. 持有并等待 → 按相同顺序获取锁
  3. 不可抢占 → 使用 tryLock() 设置超时
  4. 循环等待 → 同上

线程安全的核心:搞清楚哪些资源会被多个线程同时访问,然后给它们加锁。

基于 VitePress 构建