Java 公平锁与非公平锁详解

Java 公平锁与非公平锁详解

一、基本概念

1. 公平锁 (Fair Lock)

定义 :多个线程按照申请锁的时间顺序获取锁,即"先来先得"原则

特点

  • 线程按请求锁的时间先后顺序排队
  • 新线程到来时,会检查等待队列
  • 如果队列中有等待线程,则新线程会加入队列尾部
  • 保障资源分配的绝对公平性

2. 非公平锁 (Non-fair Lock)

定义:允许线程"插队",新线程可直接尝试获取锁,而不一定遵循请求顺序

特点

  • 新线程可以尝试"插队"直接获取锁
  • 若获取失败才进入等待队列
  • 锁释放时,可能唤醒队列中线程,也可能被新来的线程抢占
  • 吞吐量通常比公平锁更高

二、工作流程对比

公平锁工作流程:

锁空闲? 空闲 队列空 队列非空 线程A请求锁 锁状态 检查等待队列 线程A获取锁 线程A进入等待队列 线程A执行 线程B唤醒获取锁

非公平锁工作流程:

直接尝试获取 是 否 锁释放时 成功 失败 线程A请求锁 获取成功? 线程A持有锁执行 进入等待队列 尝试与队列外线程竞争

三、Java中的实现与使用

1. ReentrantLock的公平/非公平控制

java 复制代码
// 创建非公平锁(默认)
ReentrantLock nonFairLock = new ReentrantLock(); 

// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true); 

// 使用示例
public void accessResource() {
    fairLock.lock();  // 公平获取锁
    try {
        // 临界区代码
    } finally {
        fairLock.unlock();  // 确保释放锁
    }
}

2. synchronized 与公平性

  • synchronized关键字实现的是非公平锁
  • 开发者无法控制synchronized的公平性
  • 需要公平锁时必须使用ReentrantLock

四、性能对比与适用场景

特性 公平锁 非公平锁
吞吐量 较低 较高
线程等待时间 相对平均 差异较大(可能饥饿)
实现复杂度 较高 相对简单
上下文切换 较多 较少
适用场景 1. 要求严格公平 2. 线程等待时间敏感 1. 高并发场景 2. 吞吐量优先

适用场景分析:

推荐公平锁的场景

  1. 要求严格顺序执行的业务(如交易处理系统)
  2. 线程等待时间至关重要的情况
  3. 避免线程饥饿(如资源分配系统)

推荐非公平锁的场景

  1. 高并发应用(最大吞吐量优先)
  2. 锁持有时间短且操作频繁
  3. 减少线程切换开销的场景

五、底层实现原理

1. 公平锁实现机制

java 复制代码
// ReentrantLock的公平获取锁逻辑
protected final boolean tryAcquire(int acquires) {
    // 检查是否有前驱节点在等待
    if (getFirstQueuedThread() != Thread.currentThread() && 
        hasQueuedPredecessors()) {
        return false; // 队列中有等待线程,排队
    }
    // ...其他逻辑
}

2. 非公平锁实现机制

java 复制代码
// 非公平获取锁的典型逻辑
final void lock() {
    // 首先尝试直接获取锁(插队)
    if (compareAndSetState(0, 1)) 
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1); // 失败后再进入队列
}

3. CLH队列(锁的等待队列)

两种锁类型都使用CLH变体的FIFO等待队列:

  • 线程加入队列时自旋检查前驱节点状态
  • 当前驱节点释放锁时通知后续节点

六、实战示例

公平锁实例:

java 复制代码
public class FairLockExample {
    private final ReentrantLock lock = new ReentrantLock(true);
    
    public void performTask() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 获取锁");
            // 模拟操作
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    // 测试:多个线程将按启动顺序获取锁
}

非公平锁实例:

java 复制代码
public class NonFairLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void performTask() {
        // 尝试插队获取锁
        if (lock.tryLock()) {
            try {
                System.out.println(Thread.currentThread().getName() + " 插队成功");
                Thread.sleep(50);
            } finally {
                lock.unlock();
            }
        } else {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 排队获取");
                Thread.sleep(100);
            } finally {
                lock.unlock();
            }
        }
    }
}

七、如何选择合适的锁

决策因素

  1. 吞吐量需求:高并发场景优先非公平锁
  2. 公平性要求:交易系统优先公平锁
  3. 锁持有时间
    • 短时间操作:非公平锁
    • 长时间操作:公平锁(减少饥饿)
  4. 系统资源
    • CPU资源有限:非公平锁(减少切换)
    • 内存资源充足:公平锁

默认建议 :大多数场景下非公平锁是更好的选择,因为:

  • Java中synchronized默认就是非公平
  • ReentrantLock默认实现也是非公平
  • 提供更好的整体吞吐量

八、总结

对比维度 公平锁 非公平锁
排队机制 严格遵守FIFO 允许插队
吞吐量 较低 较高
响应时间 可预测 不确定
实现难度 较复杂 较简单
避免饥饿 不能

核心结论

  1. 公平锁保障绝对的请求顺序
  2. 非公平锁提供更高的吞吐量
  3. Java中锁的默认行为是非公平的(符合大多数场景)
  4. 在需要严格顺序的业务中才使用公平锁
相关推荐
孟婆来包棒棒糖~17 分钟前
Maven快速入门
java·spring boot·spring·maven·intellij-idea
jingfeng5143 小时前
C++模板进阶
java·c++·算法
杨杨杨大侠3 小时前
附录 1:[特殊字符] Maven Central 发布完整指南:从零到成功部署
java·spring boot·maven
ahauedu3 小时前
AI资深 Java 研发专家系统解析Java 中常见的 Queue实现类
java·开发语言·中间件
小厂永远得不到的男人3 小时前
基于 Spring Validation 实现全局参数校验异常处理
java·后端·架构
计算机编程小咖4 小时前
《基于大数据的农产品交易数据分析与可视化系统》选题不当,毕业答辩可能直接挂科
java·大数据·hadoop·python·数据挖掘·数据分析·spark
艾莉丝努力练剑4 小时前
【C语言16天强化训练】从基础入门到进阶:Day 7
java·c语言·学习·算法
老华带你飞4 小时前
校园交友|基于SprinBoot+vue的校园交友网站(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·校园交友网站
自强的小白5 小时前
学习Java24天
java·学习