跳转到内容

线程安全问题

来自代码酷

模板:编程概念导航

概述[编辑 | 编辑源代码]

线程安全问题(Thread Safety)指当多个线程并发访问共享资源时,程序仍能保持正确的行为和数据一致性。若未采取同步措施,可能导致竞态条件(Race Condition)、内存可见性问题或指令重排序等问题,进而引发不可预知的错误。

线程安全的核心挑战在于:

  • 原子性:操作不可被中断(如非原子操作的`i++`)
  • 可见性:线程对共享变量的修改对其他线程立即可见
  • 有序性:程序执行顺序符合代码逻辑(避免指令重排序)

典型问题场景[编辑 | 编辑源代码]

竞态条件示例[编辑 | 编辑源代码]

以下代码展示了一个经典的线程不安全案例:

  
public class Counter {  
    private int count = 0;  

    public void increment() {  
        count++; // 非原子操作(读取-修改-写入)  
    }  

    public int getCount() {  
        return count;  
    }  
}

当多个线程同时调用`increment()`时,可能发生以下情况: 1. 线程A读取`count=0` 2. 线程B也读取`count=0` 3. 两者分别执行`count+1`并写回 4. 最终结果可能是`1`而非预期的`2`

内存可见性问题[编辑 | 编辑源代码]

  
public class VisibilityProblem {  
    private boolean flag = true;  

    public void writer() {  
        flag = false; // 修改可能不会立即对其他线程可见  
    }  

    public void reader() {  
        while (flag) {  
            // 可能陷入无限循环  
        }  
    }  
}

解决方案[编辑 | 编辑源代码]

同步机制[编辑 | 编辑源代码]

方法 描述 适用场景
`synchronized` 通过互斥锁保证原子性和可见性 方法/代码块同步
`volatile` 保证可见性,禁止指令重排序 单一变量状态标志
`AtomicXXX` CAS(Compare-And-Swap)原子类 计数器等场景

synchronized示例[编辑 | 编辑源代码]

  
public class SafeCounter {  
    private int count = 0;  

    public synchronized void increment() {  
        count++;  
    }  
}

AtomicInteger示例[编辑 | 编辑源代码]

  
import java.util.concurrent.atomic.AtomicInteger;  

public class AtomicCounter {  
    private AtomicInteger count = new AtomicInteger(0);  

    public void increment() {  
        count.incrementAndGet();  
    }  
}

线程封闭[编辑 | 编辑源代码]

通过限制数据访问权限避免共享:

  • 栈封闭:局部变量线程私有
  • ThreadLocal:为每个线程创建独立副本

实际应用案例[编辑 | 编辑源代码]

电商库存扣减[编辑 | 编辑源代码]

sequenceDiagram 用户A->>服务端: 请求扣减库存(当前库存=1) 用户B->>服务端: 同时请求扣减同一商品 服务端->>数据库: 查询库存 数据库-->>服务端: 返回库存=1 服务端->>服务端: 并行执行扣减逻辑 服务端->>数据库: 两次update库存=0

解决方案:使用数据库悲观锁或分布式锁保证原子性。

深入原理[编辑 | 编辑源代码]

Happens-Before规则[编辑 | 编辑源代码]

Java内存模型定义的保证可见性的规则,包括:

  • 程序顺序规则
  • 锁规则(解锁先于后续加锁)
  • `volatile`变量规则

指令重排序[编辑 | 编辑源代码]

编译器/处理器可能优化指令顺序,通过`volatile`或`synchronized`禁止重排序: {原顺序load A → load B → store C重排序load B → load A → store C

常见误区[编辑 | 编辑源代码]

  • 认为`volatile`能替代`synchronized`(实际仅保证可见性)
  • 忽略复合操作的非原子性(如`check-then-act`)
  • 过度同步导致性能下降

最佳实践[编辑 | 编辑源代码]

1. 优先使用不可变对象(如`String`) 2. 缩小同步代码块范围 3. 使用线程安全集合(如`ConcurrentHashMap`) 4. 避免在同步块中调用外部方法

模板:编程概念页尾