Java监视器有等待队列,为什么synchronized还是非公平锁?

Java监视器有等待队列,为什么synchronized还是非公平锁?

之前在看synchronized的源码时发现了一个有趣的问题:既然Java的监视器(Monitor)底层有等待队列(Entry Set)来管理竞争锁的线程,那为什么synchronized还是实现不了公平锁呢?

其实准确来说,不是”实现不了”,而是synchronized从设计之初就选择了性能和简便性,所以压根没打算做成公平锁。

监视器的基本结构

每个Java对象都关联着一个监视器,包含几个关键部分:

  • Owner: 当前持有锁的线程
  • Entry Set: 等待获取锁的线程队列
  • Wait Set: 调用wait()后进入等待状态的线程队列

线程获取synchronized锁的流程:

  1. 锁没人用(Owner为空)→ 直接拿到锁
  2. 锁被占用 → 进入Entry Set排队等待

synchronized为什么是非公平的?

虽然有Entry Set这个队列,但synchronized的锁分配策略就是非公平的,主要原因:

1. 新线程可以”插队”

这是最关键的点。当锁释放时,新来的线程可以直接和Entry Set中等待的线程竞争,而不需要排队。

举个例子:

  • 线程A释放锁
  • 线程B在Entry Set中等待
  • 线程C刚好这时候要获取锁
  • 结果:线程C可能直接抢到锁,跳过了线程B

为什么要这样设计?性能考虑。让新线程直接竞争可以减少线程上下文切换的开销,提升整体吞吐量。

2. JVM的各种锁优化

现代JVM对synchronized做了很多优化,这些优化进一步加剧了非公平性:

偏向锁(Biased Locking)

  • 锁会”偏心”第一个获取它的线程
  • 后续如果没有竞争,直接进入同步代码,根本不走队列

轻量级锁(Lightweight Locking)

  • 通过CAS操作尝试获取锁
  • 失败后会自旋(spin)而不是直接入队
  • 新线程可能通过自旋抢到锁

自旋锁(Spin Locking)

  • Entry Set中的线程可能自旋尝试获取锁
  • 和新线程竞争时没有顺序保证

3. 队列唤醒策略不保证FIFO

Entry Set中线程的唤醒顺序并不是先进先出的。JVM实现(比如HotSpot)可能采用:

  • 随机唤醒
  • 策略性唤醒(优先唤醒最近活跃的线程)

而不是按请求顺序分配锁。

公平锁是什么样的?

看看ReentrantLock的公平锁实现就明白了:

1
2
3
4
5
6
7
8
ReentrantLock fairLock = new ReentrantLock(true); // true表示公平锁

fairLock.lock();
try {
// 临界区代码
} finally {
fairLock.unlock();
}

公平锁的策略:

  • 锁释放时,优先唤醒Entry Set中等待时间最长的线程
  • 新线程必须排队,不能插队

synchronized的局限:

  • 没有公平模式选项
  • 无法控制Entry Set中线程的唤醒顺序
  • 底层实现始终是非公平的

为什么synchronized选择非公平?

从技术角度看,Entry Set完全可以设计成FIFO队列,JVM也完全可以按FIFO顺序唤醒线程。synchronized实现公平锁在技术上没有障碍。

真正的原因是设计取舍:

性能优先的考虑

减少线程切换开销

1
2
3
场景:线程A释放锁后
- 非公平锁:新线程C可能直接通过自旋抢到锁,避免唤醒线程B
- 公平锁:必须先唤醒队列中的线程B,涉及操作系统级的线程调度

提升吞吐量

  • 非公平锁在高并发场景下允许更高效的锁竞争
  • 减少CPU空闲时间,最大化利用率

JVM优化策略的配合

现代JVM对synchronized的各种优化(偏向锁、轻量级锁、自旋锁等)都更适合非公平锁的特性。如果强行实现公平锁,这些优化的效果会大打折扣。

总结

synchronized的非公平性不是技术限制,而是设计选择。Java在设计时优先考虑了性能和简便性,选择牺牲公平性来换取更好的吞吐量。

如果你的业务场景对公平性有严格要求,可以考虑使用ReentrantLock的公平模式。但要记住,公平锁通常会带来一定的性能损失。

什么时候需要公平锁?

  • 防止线程饥饿的场景
  • 对响应时间有严格要求的系统
  • 需要严格按顺序处理请求的业务逻辑

什么时候用synchronized就够了?

  • 大部分普通的同步场景
  • 追求高吞吐量的系统
  • 对公平性要求不高的业务逻辑