Java中提供的synchronized只能是非公平锁。
Java中提供的ReentrantLock,ReentrantReadWriteLock可以实现公平锁和非公平锁
公平锁
公平锁,先到先得;多个线程按顺序排队获取锁,每个线程获取机会均等,但永远只有队列首位线程能获取到锁。
优点:每个线程等待一段时间后,都有执行的机会,不至于出现某个线程饿死在队列中。
缺点:是队列里面除了第一个线程,其他的线程都会阻塞,cpu 唤醒阻塞线程的开销会很大
非公平锁
多个线程(不管是不是队列首位)去获取锁时会尝试直接获取锁,能获取到就执行任务,否则乖乖排队。代表实现是CAS比较并交换;
优点:获取锁更加灵活、吞吐量大、减少CPU唤醒线程的开销。
缺点:会出现某些线程永远获取不到锁,饿死在队列中,最终由 JVM 回收
注意锁的获取有三个条件:看状态state是否是0,当前线程是否为null; 是否排在队首
公平锁跟非公平锁加锁的逻辑差不多,唯一就是公平加锁的 if 判断中多了 hasQueuedPredecessors 是否队首
1 | protected final boolean tryAcquire(int acquires) { |
推荐博客:公平锁和非公平锁
公平锁非公平锁对竞争的影响?
公平锁降低吞吐、减少饥饿;
非公平锁提高吞吐、放大竞争,但可能导致饥饿
公平锁的竞争模型
先到先得(FIFO)
实现方式(以 ReentrantLock 为例):
- 每次获取锁前先检查 AQS 队列是否有人排队
- 有人排队就老实排队
- 不允许插队
竞争特征
- 竞争是有序的
- 线程按顺序被唤醒
- 很少发生饥饿
非公平锁的竞争模型
只要你抢得到,你就赢
实现方式:
- 不管队列里有没有人
- 直接 CAS 抢锁
- 抢到了就插队成功
竞争特征
- 竞争是无序的
- 新线程可能反复插队
- 老线程可能长期得不到锁
公平锁非公平锁使用场景?
适合公平锁的场景:
- 请求必须有响应保证
- 任务耗时差异极大
- SLA / 合规要求
对于吞吐 > 公平的业务场景,推荐使用非公平锁
总结
公平锁和非公平锁的核心区别在竞争策略。
公平锁严格 FIFO,降低饥饿但牺牲吞吐;
非公平锁允许插队,通过减少线程切换提高性能。
在高并发核心链路中通常选非公平锁,在需要响应保证或防止长期饥饿的场景才用公平锁