本文深入剖析静态共享锁与每个线程独有实例锁在多线程环境下的同步效果差异,通过代码示例阐明为何前者能实现串行执行而后者导致竞态重叠。
本文深入剖析静态共享锁与每个线程独有实例锁在多线程环境下的同步效果差异,通过代码示例阐明为何前者能实现串行执行而后者导致竞态重叠。
在 Java 并发编程中,synchronized 块的同步效果完全取决于所用锁对象的身份(identity)和可见性范围,而非其变量名或声明位置。核心原则是:只有多个线程竞争同一把锁(即同一个 Object 实例),才能产生互斥阻塞;若各持一把不同的锁,则形同未加锁。
关键对比:静态锁 vs 实例锁
在您提供的代码中,存在两个锁对象:
MyClass.lock:private static Object lock = new Object();
✅ 静态、类级别、全局唯一 —— 所有 Worker 实例(即所有线程)共享同一个锁对象。Worker.lock2:private Object lock2 = new Object();
❌ 实例级别、每个 Worker 独有 —— 每个线程运行的 Worker 对象都拥有自己独立的 lock2 实例,彼此无关。
因此:
| 锁类型 | 锁对象数量 | 线程是否竞争同一锁 | 同步效果 |
|---|---|---|---|
| MyClass.lock | 1 个(静态) | 是 ✅ | 串行执行,输出严格有序 |
| Worker.lock2 | 5 个(每线程1个) | 否 ❌ | 无实际互斥,输出高度交错 |
代码验证:观察执行差异
以下精简复现关键逻辑(含日志增强以凸显竞态):
立即学习“Java免费学习笔记(深入)”;
class MyClass {
private static final Object GLOBAL_LOCK = new Object(); // 明确命名,强调共享性
public static void main(String[] args) {
for (int i = 1; i <= 3; i++) {
new Thread(new Worker(), "T-" + i).start();
}
}
private static class Worker implements Runnable {
private int runCount = 1;
private final Object LOCAL_LOCK = new Object(); // 每个Worker实例独有
@Override
public void run() {
// ✅ 使用静态锁:强制串行
for (int i = 0; i < 3; i++) {
synchronized (GLOBAL_LOCK) {
System.out.format("[%s] count = %d%n", Thread.currentThread().getName(), runCount++);
// 模拟耗时操作(非必须,但有助于观察调度)
try { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
// ❌ 使用实例锁:无同步效果(取消注释可验证)
/*
for (int i = 0; i < 3; i++) {
synchronized (LOCAL_LOCK) {
System.out.format("[%s] (local) count = %d%n", Thread.currentThread().getName(), runCount++);
}
}
*/
}
}
}预期输出(使用 GLOBAL_LOCK):
[T-1] count = 1 [T-1] count = 2 [T-1] count = 3 [T-2] count = 4 [T-2] count = 5 [T-2] count = 6 [T-3] count = 7 [T-3] count = 8 [T-3] count = 9
→ 全局计数 runCount 严格递增,且每个线程连续输出 3 行(因锁持有期间其他线程被阻塞)。
若改用 LOCAL_LOCK:
输出将呈现典型竞态:count 值重复、乱序,例如 [T-1] count = 1, [T-2] count = 1, [T-1] count = 2 —— 因为每个线程只与自己“同步”,对其他线程毫无影响。
重要注意事项
- ? synchronized(obj) 的语义是 “获取 obj 的 intrinsic lock”,本质是 JVM 对该对象监视器(monitor)的操作。不同对象 → 不同监视器 → 无互斥。
- ? lock2 虽在 Worker 类中声明为 private,但因其是非静态字段,每次 new Worker() 都创建全新对象,故 lock2 实例天然不共享。
- ? 若需让 lock2 生效,应将其提升为 static(如 private static final Object lock2 = new Object();),或通过构造器传入同一共享锁实例。
- ? 推荐实践:优先使用 private static final Object LOCK = new Object(); 定义显式锁,避免误用 this 或 getClass() 等易引发意外共享的锁对象。
总结
线程同步的有效性不在于“是否写了 synchronized”,而在于所有参与协作的线程是否共同争抢同一把物理锁。静态锁提供跨实例的全局协调能力,实例锁则仅限单个对象内部。理解锁对象的生命周期与作用域,是编写正确并发程序的第一道基石。










