
synchronized 释放锁不等于“立刻唤醒并调度等待线程”;JVM 和操作系统优先保障执行效率而非公平性,当前线程可能在锁释放后迅速重新进入方法体,导致观察到“先打印 enter sell method 再抢到锁”的现象。
synchronized 释放锁不等于“立刻唤醒并调度等待线程”;jvm 和操作系统优先保障执行效率而非公平性,当前线程可能在锁释放后迅速重新进入方法体,导致观察到“先打印 `enter sell method` 再抢到锁”的现象。
在 Java 多线程中,synchronized 代码块的锁释放(exit monitorenter)仅表示该 monitor 变为可竞争状态,并不触发强制线程切换或公平唤醒机制。你观察到的现象——例如 Thread-2 执行完 synchronized 块后,输出 "Thread-0 enter sell method" 而非 "Thread-0 enter sync code block!"——根本原因在于:锁释放与下一线程成功加锁之间存在调度间隙,而原线程极可能凭借 CPU 局部性优势,在锁空闲瞬间再次获得 CPU 时间片,抢先完成 sell() 方法的剩余逻辑(如 while 循环判断、再次调用 sell()),甚至再次抵达 synchronized 块入口前就输出了日志。
这并非偶然,而是由三层机制共同决定的:
JVM 的 monitor 实现是非公平的:
synchronized底层基于操作系统互斥量(如 pthread_mutex_t)或自旋+阻塞混合策略,其默认行为是“抢占优先”(barging),即新到达的线程(包括刚释放锁又重入的同一线程)与等待队列中的线程竞争同一把锁,且前者往往因无上下文切换开销而胜出。OS 调度器以吞吐优先:现代操作系统(Linux/Windows/macOS)的线程调度器目标是最大化 CPU 利用率,而非线程间绝对公平。若
Thread-2刚释放锁便立即被调度继续运行(例如仍在同一 CPU 核上运行),它比被唤醒的Thread-0更快执行到while (ticketNum != 0)和下一次sell()调用,自然先输出"enter sell method"。
Alibabacloud Sdk Client Initialization For Java下载在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
日志位置暴露了执行时序真相:关键注意
System.out.println(... "enter sell method")位于synchronized块外部。这意味着:- 线程可在未持有锁时自由执行该语句;
- 锁释放后,原线程无需等待,直接执行循环条件判断 → 再次调用
sell()→ 先打印日志 → 才尝试获取锁。
✅ 正确理解流程(以 Thread-2 为例):
// Thread-2 第一次执行结束:
synchronized (this) { ... } // ← 锁在此刻释放
} // ← 离开 sell() 方法体?不!它还在 while 循环内!
// 紧接着执行:
while (ticketNum != 0) { // ← 仍为 true(如 ticketNum=9)
sell(); // ← 再次进入 sell() 方法
} // ↑ 这里立刻触发下一轮:先打印 "enter sell method"
⚠️ 注意事项:
- 不要依赖
synchronized的“唤醒顺序”实现业务逻辑(如轮询顺序、严格 FIFO); - 若需公平性(如防止线程饥饿),应显式使用
ReentrantLock(true),但需权衡性能损耗(公平模式需维护等待队列,增加 CAS 开销和上下文切换); - 日志语句的位置极大影响可观测行为——将
"enter sell method"移入synchronized块内,即可确保其与锁获取强绑定,但会掩盖真实调度延迟。
? 总结:
你看到的不是 bug,而是并发系统高效性的体现。synchronized 提供的是互斥性(mutual exclusion)和可见性(visibility),而非调度公平性(fair scheduling)。设计多线程程序时,应基于“协作式并发”思维:用锁保护共享状态,用合理结构(如生产者-消费者模式)减少争用,而非假设线程会被“准时唤醒”。真正的健壮性,源于对竞态条件的主动规避,而非对调度器行为的被动期待。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










