Java 并发编程成长:解决线程安全问题的 7 个实战方案(含 volatile 原理)
·
Java 并发编程成长:解决线程安全问题的 7 个实战方案
一、线程安全问题的本质
当多个线程并发访问共享资源时,由于执行顺序不确定性和内存可见性问题,可能导致数据不一致。核心矛盾在于:
- 竞态条件(Race Condition):多个线程对共享数据的非原子操作
- 内存可见性:线程本地缓存与主内存的数据不一致
二、7 个实战解决方案
-
同步代码块(synchronized)
public class Counter { private int count = 0; public synchronized void increment() { count++; // 原子操作 } }- 原理:通过对象内置锁(Monitor)保证互斥访问
- 适用场景:方法级或代码块级的简单同步
-
可重入锁(ReentrantLock)
private final ReentrantLock lock = new ReentrantLock(); public void safeWrite() { lock.lock(); try { // 临界区操作 } finally { lock.unlock(); } }- 优势:支持公平锁、可中断锁、超时锁等高级特性
- 适用场景:需要细粒度控制的复杂同步
-
原子类(Atomic Classes)
AtomicInteger atomicCount = new AtomicInteger(0); void safeIncrement() { atomicCount.incrementAndGet(); // CAS 操作 }- 原理:基于 CPU 的 Compare-And-Swap 指令实现无锁更新
- 性能:高并发场景下性能优于锁
-
volatile 关键字
private volatile boolean flag = false;- 原理详解:
- 可见性保证:强制所有读写直接操作主内存
- 禁止指令重排序:通过内存屏障(Memory Barrier)
- 内存语义:写操作 happens-before 后续读操作
- 适用场景:状态标志位、单次写入的共享变量
- 原理详解:
-
线程局部存储(ThreadLocal)
private static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));- 原理:每个线程持有变量的独立副本
- 适用场景:线程专属对象(如数据库连接)
-
不可变对象(Immutable Objects)
public final class ImmutablePoint { private final int x; private final int y; // 无 setter 方法 }- 原理:对象状态在构造后不可修改
- 线程安全:天然无需同步
-
并发容器(Concurrent Collections)
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.compute("key", (k, v) -> (v == null) ? 1 : v + 1);- 推荐容器:
ConcurrentHashMap、CopyOnWriteArrayList - 优势:内置分段锁或写时复制技术
- 推荐容器:
三、volatile 深度解析
-
内存可见性原理
- 写操作:立即刷新到主内存,并失效其他线程的缓存
- 读操作:强制从主内存重新加载
- 内存屏障:禁止编译器/CPU 将 volatile 操作与其他内存操作重排序
-
与 synchronized 对比
特性 volatile synchronized 原子性 仅保证单次读写 保证代码块原子性 阻塞行为 非阻塞 阻塞 适用场景 状态标志 复合操作 -
典型使用场景
- 双重检查锁定(Double-Checked Locking)
class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { // 锁定 if (instance == null) { // 第二次检查 instance = new Singleton(); // volatile 防止重排序 } } } return instance; } }
四、方案选择建议
- 优先考虑:不可变对象 > 原子类 > 并发容器
- 性能敏感场景:避免 synchronized,改用 ReentrantLock 或 CAS
- 状态标志:首选 volatile(如
while(!shutdownFlag)) - 复合操作:必须使用锁或原子变量(如
i++)
实践提示:通过
javap -c反编译观察字节码,可验证 volatile 的内存屏障指令(如 ACC_VOLATILE 标志)。
更多推荐
所有评论(0)