虚拟线程加锁不会“固定”在平台线程上,而是因synchronized非协作式阻塞导致调度僵化;所谓“固定变量风险”实为锁对象不稳或同步块副作用引发的共享风险。

锁在虚拟线程中不会让线程“固定”,但会让持有锁的虚拟线程阻塞调度,进而导致其他等待该锁的虚拟线程被挂起——这不是线程被“固定”在某个平台线程上,而是锁本身成了调度瓶颈。
为什么说“固定”是误解?
虚拟线程本质是轻量、可挂起、由 JVM 调度的协程式线程。它不绑定操作系统线程,也不会因加锁而被“钉死”在某个平台线程上。真正的问题在于:synchronized 是非协作式阻塞——它不触发虚拟线程让出机制。
- 遇到 I/O 或
Thread.sleep()时,虚拟线程会自动挂起,把底层平台线程交还给调度器 - 但进入
synchronized块后,即使内部执行耗时计算或休眠,JVM 也不会主动挂起该虚拟线程 - 只要锁未释放,其他竞争该锁的虚拟线程就只能排队等待,无法被调度执行
变量风险:不是锁“固定”了变量,而是同步范围不当放大了共享风险
所谓“固定变量风险”,实际是指:错误地将可变共享变量作为锁对象,或在同步块中引入非原子副作用,从而削弱同步效果,甚至让锁形同虚设。
-
synchronized(count)中若count是Integer类型,每次自增都会触发装箱,导致锁对象变更,多个线程实际锁的是不同对象 - 同步块内调用非线程安全方法(如修改外部集合、写日志、发起 HTTP 请求),可能让临界区失控
- 用
this或类对象锁整个实例/类,却只保护局部状态,造成过度同步或保护不足
如何避免锁引发的调度僵化与变量误用?
核心原则:锁要小、对象要稳、操作要纯。
- 锁对象必须是生命周期稳定、不可变或专用于同步的私有 final 对象,例如
private final Object lock = new Object(); - 同步块内只做状态更新等轻量、无副作用的操作;I/O、睡眠、复杂计算一律移出
- 优先用原子类替代简单计数类场景(如
AtomicInteger),消除锁依赖 - 对复杂逻辑,考虑
ReentrantLock配合tryLock()或异步拆分,避免无限期阻塞
一个典型反例与修正对比
危险写法:
private Integer counter = 0;
public void badInc() {
synchronized(counter) { // 锁对象会变!
counter++;
Thread.sleep(100); // 阻塞调度,且无意义地拖长临界区
}
}
安全写法:
private final Object lock = new Object();
private int counter = 0; // 基本类型,避免装箱
public void goodInc() {
synchronized(lock) {
counter++; // 纯内存操作,毫秒级完成
}
// sleep、log、IO 等全部放在这里,不参与同步
}










