并发面试题:线程安全不是背出来的
面试时问并发,很多人能把 synchronized 和 Lock 的区别背出来,但被追问:你在实际项目里遇到过线程安全问题吗?怎么排查的? 就答不上来了。
原理要学,实践更重要。
synchronized vs Lock
| 对比项 | synchronized | Lock |
|---|---|---|
| 语法 | 关键字,自动释放 | 接口,需手动释放 |
| 释放 | 代码块结束自动释放 | 必须 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<>(100), // 队列大小
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);常见追问
Q: synchronized 锁的是什么?
普通方法锁的是 this(即当前对象),静态方法锁的是 Class 对象。
Q: 死锁怎么排查?
bash
# 1. 先找到 Java 进程
jps -l
# 2. 查看线程堆栈
jstack <pid>
# 搜索 "Deadlock" 关键字Q: 怎么避免死锁?
四个条件破坏一个即可:
- 互斥 → 无法破坏
- 持有并等待 → 按相同顺序获取锁
- 不可抢占 → 使用
tryLock()设置超时 - 循环等待 → 同上
线程安全的核心:搞清楚哪些资源会被多个线程同时访问,然后给它们加锁。
