synchronized通过对象锁确保读写操作串行执行,避免脏读:当多个线程共用同一对象时,对实例变量的读写方法均加synchronized,使它们竞争同一把锁,从而保证读取的是完整写入后的一致状态。

Java 中用 synchronized 避免脏读,核心是确保“读”和“写”操作对共享实例变量的访问是原子且串行的——不能一个线程在写一半时,另一个线程就去读。
脏读发生的典型场景
多个线程共用同一个对象实例,其中:一个线程修改实例变量(比如先改 name,再休眠,最后改 password),另一个线程在它还没改完时就读取了这个对象的状态。此时读到的 name 和 password 可能来自不同时间点,组合起来就是“脏数据”。例如:
- 线程 A 执行
setValue("B", "BB"),把username="B"后 sleep 5 秒,还没来得及设password; - 线程 B 此时调用
getValue(),读到的是username="B"但password="AA"(旧值); - 输出变成
username=B, password=AA,逻辑错乱——这就是脏读。
synchronized 方法锁住整个对象
给读、写方法都加上 synchronized,让它们竞争同一把对象锁:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 写方法(如
setValue)加锁 → 其他线程无法同时进入任何该对象的 synchronized 方法; - 读方法(如
getValue)也加锁 → 它必须等写方法完全执行完、释放锁后才能开始读; - 这样就保证了“读到的一定是完整写入后的最新一致状态”,不会出现中间态。
注意锁的作用范围
关键不是“方法有没有 synchronized”,而是“是否作用于同一把锁”:
立即学习“Java免费学习笔记(深入)”;
- 如果两个线程操作的是 不同对象实例(比如各自 new 一个 PublicVar),那即使方法加了 synchronized,也互不影响——因为锁是对象级别的,各锁各的;
- 如果变量是 局部变量(方法内定义),天然线程安全,不存在脏读;
- 如果变量是 static,synchronized 实例方法锁不住它,得用
synchronized(PublicVar.class)或 static 同步方法。
更灵活的控制:用同步代码块
不必锁整个方法,只锁真正需要保护的临界区:
- 减少锁持有时间,提升并发度;
- 例如只对赋值和打印之间那段逻辑加锁:
synchronized(this) { this.username = u; this.password = p; }; - 读操作同样包裹在
synchronized(this)块里,确保可见性和原子性。

















