一、线程安全的本质问题
多线程安全问题源于共享资源的竞态条件(Race Condition),即多个线程同时读写同一资源时,因执行顺序不确定导致的数据不一致或异常。
典型场景:
- 计数器递增(
count++非原子操作) - 银行账户转账(存款与取款并发)
- 缓存读写(读取时可能被其他线程修改)
二、核心解决方案
1. 互斥同步(阻塞同步)
通过锁机制确保同一时刻只有一个线程访问共享资源。
- 语言原生锁:
- Java:
synchronized关键字、ReentrantLock - Python:
threading.Lock、RLock - C++:
std::mutex、std::lock_guard
- Java:
- 示例(Java):
public class Counter { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; // 临界区 } finally { lock.unlock(); } } }
2. 无锁编程(非阻塞同步)
通过原子操作(Atomic Operation)避免锁的开销。
- 原子类:
- Java:
AtomicInteger、AtomicReference - Python:
threading.Lock(低层级可用ctypes调用原子操作) - C++:
std::atomic
- Java:
- 示例(Java):
public class Counter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); // 原子操作,无需锁 } }
3. 线程封闭(Thread Confinement)
将数据限制在单个线程内,避免共享。
- 栈封闭:局部变量仅在方法内可见(如 Java 的
ThreadLocal)。 - 示例(Java):
public class ConnectionManager { private static final ThreadLocal<Connection> connection = new ThreadLocal<>(); public static Connection getConnection() { if (connection.get() == null) { connection.set(DriverManager.getConnection(URL)); // 每个线程独立创建连接 } return connection.get(); } }
4. 不可变对象(Immutable Objects)
使用不可变类(如 Java 的 String、Integer),确保数据创建后无法修改。
- 示例(Java):
public final class ImmutableUser { // 类用 final 修饰 private final String name; private final int age; public ImmutableUser(String name, int age) { this.name = name; this.age = age; } // 无 setter 方法,仅提供 getter public String getName() { return name; } public int getAge() { return age; } }
三、问题
1. 问:synchronized 和 ReentrantLock 的区别?
- 答:
- 语法层面:
synchronized是关键字,ReentrantLock是类; - 灵活性:
ReentrantLock支持公平锁、可中断锁、尝试锁; - 性能:高竞争场景下
ReentrantLock更优(JDK 1.6 后synchronized优化显著); - 释放方式:
synchronized自动释放,ReentrantLock需手动调用unlock()。
- 语法层面:
2. 问:如何实现线程安全的单例模式?
- 答:
- 静态内部类(推荐):
public class Singleton { private static class Holder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return Holder.INSTANCE; // 类加载时初始化,线程安全 } } - 双重检查锁定(DCL):
public 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. 问:CAS(Compare-and-Swap)的原理与优缺点?
- 答:
- 原理:原子操作,比较内存值与预期值,相等则更新,否则重试;
- 优点:无锁,避免线程切换开销;
- 缺点:
- 循环时间长可能导致 CPU 开销大;
- 只能保证单个变量的原子性;
- 存在 ABA 问题(需通过
AtomicStampedReference解决)。
4. 问:什么是死锁?如何避免?
- 答:
- 死锁条件:互斥、占有并等待、不可抢占、循环等待;
- 避免策略:
- 按顺序获取锁(如统一先锁 A 再锁 B);
- 设置锁超时(
tryLock); - 减少锁粒度(如使用分段锁)。
四、性能优化与最佳实践
-
锁优化策略
- 减小锁粒度:如
ConcurrentHashMap的分段锁(JDK 1.8 前); - 锁粗化:避免频繁加锁解锁(如循环内的同步操作);
- 偏向锁/轻量级锁:JVM 对
synchronized的优化(根据竞争程度动态调整)。
- 减小锁粒度:如
-
线程安全容器选择
- 读多写少:
CopyOnWriteArrayList、ConcurrentHashMap; - 阻塞队列:
LinkedBlockingQueue、ArrayBlockingQueue; - 并发工具类:
CountDownLatch、CyclicBarrier、Semaphore。
- 读多写少:
-
实战建议
- 优先使用成熟框架:如 Java 的
java.util.concurrent包; - 测试线程安全:使用压力测试工具(如 JMH)检测竞态条件;
- 文档化线程安全:明确标注类/方法是否线程安全(如
@ThreadSafe)。
- 优先使用成熟框架:如 Java 的