
本文深入解析为何使用静态共享锁(static object)可实现线程互斥执行,而使用每个 worker 实例独有的 lock2 对象则导致同步失效、输出重叠——核心在于锁对象的“唯一性”与“可见范围”。
本文深入解析为何使用静态共享锁(static object)可实现线程互斥执行,而使用每个 worker 实例独有的 lock2 对象则导致同步失效、输出重叠——核心在于锁对象的“唯一性”与“可见范围”。
在 Java 多线程编程中,synchronized 块的同步效果完全取决于所锁定对象的引用是否相同。若多个线程竞争同一把“钥匙”(即同一个对象实例),则它们将被强制串行执行;反之,若每线程都持有一把独立的“钥匙”,则同步形同虚设。
? 关键对比:静态锁 vs 实例锁
lock(静态共享锁)
定义为 private static Object lock = new Object();,属于类级别,所有 Worker 实例(及对应线程)共享唯一引用。当任一线程进入 synchronized(lock) 块时,其他线程必须等待该锁释放,从而保证 System.out.format(...) 操作严格串行,输出呈现清晰的顺序性(如 Thread 1: runCount = 1 → Thread 1: runCount = 2 → Thread 2: runCount = 3…)。lock2(实例独占锁)
定义为 private Object lock2 = new Object();,属于每个 Worker 实例的成员变量。由于每次 new Worker() 都创建全新对象,5 个线程各自持有不同内存地址的 lock2 实例(即 5 把互不相关的钥匙)。因此 synchronized(lock2) 实际上是 5 个互不干扰的临界区,线程可并行进入,导致 runCount++ 和 System.out.format 交叉执行,输出严重重叠(如 Thread 2: runCount = 1 与 Thread 1: runCount = 1 同时出现)。
✅ 正确实践:确保锁对象全局唯一且可见
若需保护共享状态(如计数器 runCount),应使锁对象满足两个条件:
- 唯一性:所有竞争线程必须访问同一对象引用;
- 可见性:该对象需在多线程间安全发布(静态字段天然满足)。
private static final Object SHARED_LOCK = new Object(); // 推荐:static + final
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (SHARED_LOCK) { // 所有线程争抢同一把锁
System.out.format("s%s: runCount = %d\n",
Thread.currentThread().getName(), runCount++);
}
}
}⚠️ 注意事项:
- ❌ lock2 不可用于保护跨实例共享状态(如 runCount 是实例变量,但多个线程修改它仍需统一协调);
- ✅ 若 runCount 改为 static int runCount,则更需配合 static 锁,否则仍存在竞态;
- ? 更优替代:对简单计数场景,优先考虑 AtomicInteger 等无锁原子类,兼顾性能与安全性。
? 总结
同步的有效性不取决于 synchronized 语法本身,而完全由锁对象的生命周期和作用域决定。静态锁(类级别)实现跨线程互斥,实例锁(对象级别)仅在单实例内有效。理解这一本质,是写出正确并发代码的第一步。
立即学习“Java免费学习笔记(深入)”;










