synchronized通过“互斥+内存可见性”解决竞态条件和脏读:锁住临界区实现串行执行,借助monitor和happens-before规则确保解锁前刷新主存、加锁后重读最新值;锁对象须一致稳定,推荐private final object;读写操作需成对同步。

synchronized 保证多线程执行安全,核心在于用“互斥 + 内存可见性”封住两个关键漏洞:竞态条件和脏读。
锁住临界区,强制串行执行
多个线程同时读写同一个共享变量时,如果操作不是原子的(比如 count++ 实际分“读-改-写”三步),就可能相互穿插,导致结果丢失。synchronized 把这类代码划为“临界区”,每次只允许一个线程进入——其他线程必须等当前线程执行完并释放锁后才能尝试获取锁。这就从执行顺序上消除了交错干扰。
- 修饰实例方法 → 锁的是当前对象(this)
- 修饰静态方法 → 锁的是该类的 Class 对象
- 修饰同步代码块 → 锁的是括号内指定的对象,粒度更可控
借助 Monitor 机制保障内存可见性
仅靠互斥还不够。假设线程 A 修改了变量但一直留在 CPU 缓存里没刷回主存,线程 B 即使拿到锁、进入同步块,也可能读到旧值。synchronized 在加锁和解锁时,会触发 JVM 的“happens-before”规则:
- 解锁前,所有对共享变量的修改都强制刷新到主内存
- 加锁后,线程会清空本地缓存,重新从主内存加载最新值
这确保了“写后读”成立:一个线程写完,另一个线程才可能读到这个新值。
锁对象必须一致且稳定
锁失效往往不是语法写错,而是锁对象出问题:
- 用不同实例调用 synchronized 实例方法 → 各自锁各自的 this,互不干扰
- 锁对象被重新赋值(如 synchronized(list); list = new ArrayList())→ 后续加锁对象已变,形同虚设
- 用 String 或 Integer 等常量/装箱对象作锁 → 因字符串池或缓存机制,实际锁的不是你预期的那个对象
推荐用 private final Object lock = new Object(); 这种私有、不可变、专属的锁对象。
读写操作需成对同步
只给写操作加锁,读操作裸奔,依然不安全:
- 非 volatile 的普通读可能被重排序或读到过期缓存值
- 即使写操作同步了,读操作没同步,JVM 不保证其能看到最新结果
- 正确做法:读和写都进入同一把锁保护的同步块(或同步方法)
例如计数器的 get() 和 increment() 都应同步,否则可能读到中间态或过期值。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











