1. 多线程安全问题的本质与应对策略
多线程编程中最核心的问题就是线程安全。经过多年Java开发实践,我发现90%以上的多线程bug都源于对共享资源的并发访问。当多个线程同时读写同一块内存区域时,如果没有适当的同步机制,就会出现数据竞争(Data Race)问题。
1.1 线程安全问题的根源
线程安全问题本质上源于三个特性被破坏:
- 原子性破坏:一个操作被线程调度器打断,执行到一半被其他线程介入
- 顺序性破坏:代码执行顺序与预期不符(指令重排序)
- 可见性破坏:一个线程的修改对另一个线程不可见(CPU缓存导致)
举个例子,经典的i++问题:
java复制// 看似简单的操作实际上包含三个步骤:
// 1. 读取i的值
// 2. 将值加1
// 3. 写回新值
// 这三个步骤如果不加锁,就可能被其他线程打断
1.2 解决线程安全的三大策略
1.2.1 避免共享策略
这是最彻底的解决方案,完全规避了并发问题:
- 线程封闭:将对象限制在单个线程内(如ThreadLocal)
- 无状态设计:不使用任何成员变量,只使用局部变量
- 不可变对象:使用final修饰的不可变对象(如String)
实际经验:在Web开发中,Servlet设计成无状态的正是为了避免线程安全问题。这也是为什么Spring的Controller默认是单例但方法参数都是线程安全的。
1.2.2 只读共享策略
当确实需要共享数据时,可以设计成只读模式:
java复制// 使用Collections.unmodifiableXXX创建不可变集合
Map<String, String> sharedMap = Collections.unmodifiableMap(originalMap);
// 使用final修饰的不可变对象
final class ImmutablePoint {
private final int x;
private final int y;
// 省略构造方法和getter
}
1.2.3 同步控制策略
当必须进行共享修改时,就需要同步机制:
| 同步机制 | 适用场景 | 特点
