Java并发编程中什么是线程安全_核心原理说明

线程安全指多线程访问共享资源时行为符合预期、结果确定且无数据污染;i++因非原子性(读-改-写三步)、可见性缺失和有序性错乱而不安全;volatile仅解决前两者,synchronized通过Monitor实现原子性、可重入与自动释放;AtomicInteger基于CAS适用于单变量原子操作,但不支持多步协同;根本难点在于识别隐式共享与合理控制锁粒度。

线程安全不是“加了锁就安全”,而是指多个线程同时访问同一对象或变量时,**程序行为始终符合预期,结果确定、无数据污染**。比如一个计数器被 10 个线程各加 100 次,最终必须是 1000 —— 少了,就是不安全;多了,也是不安全。

为什么 i++ 不是线程安全的?

表面看是一行代码,实际分三步:读取 i计算 i+1写回 i。两个线程可能同时读到 i = 5,都算出 6,再都写回去,结果还是 6(本该是 7)。这就是原子性被破坏。

  • 可见性缺失:线程 A 修改了 count,但没及时刷到主内存,线程 B 还在用自己缓存的老值
  • 有序性错乱:JVM 或 CPU 可能重排序指令,比如把“初始化对象”和“发布引用”调换顺序,导致其他线程看到未构造完成的对象(典型单例双重检查锁漏洞)
  • volatile 能解决可见性和有序性,但不能修复 i++ 这种复合操作的原子性

synchronized 怎么真正起作用?

它背后是 JVM 的监视器(Monitor)机制:每个 Java 对象都关联一个 Monitor,monitorentermonitorexit 字节码控制进入/退出。一旦某个线程拿到锁,其他线程只能排队等待。

  • 修饰实例方法 → 锁的是 this 对象
  • 修饰静态方法 → 锁的是 ClassName.class
  • 修饰代码块 → 可指定任意对象作锁,更灵活,也更易避免锁粒度过大
  • 支持可重入:同一线程可重复获取同一把锁

    ,不会死锁
  • 自动释放:无论正常结束还是抛异常,锁都会释放(这点比 ReentrantLock 更省心)

AtomicInteger 替代锁,靠谱吗?

靠谱,但只适用于“单一变量的简单操作”。它底层靠 CPU 的 CAS(Compare-And-Swap)指令实现无锁并发,比如 incrementAndGet() 是原子的,不会丢更新。

  • 优势:无锁、无上下文切换开销,高并发低竞争场景性能明显优于 synchronized
  • 局限:无法用于多变量协同逻辑(如“先查余额,够才扣款”这种两步操作),CAS 会失败重试,高竞争下可能自旋耗 CPU
  • 别误用:volatile int count + count++ 仍是线程不安全的 —— volatile 不保原子性

真正难的从来不是“怎么加锁”,而是判断哪些共享状态需要保护、锁的范围是否过宽、有没有隐式共享(比如传入的集合参数被多个线程共用)。线程安全问题往往在压测或上线后才暴露,因为竞态条件依赖于线程调度的偶然性 —— 这也正是它最危险的地方。