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. 在需要严格顺序的业务中才使用公平锁
相关推荐
zzc9212 分钟前
不同程度多径效应影响下的无线通信网络电磁信号仿真数据生成程序
网络·matlab·数据集·无线信道·无线通信网络拓扑推理·多径效应
1688red29 分钟前
IPv4编址及IPv4路由基础
运维·网络·华为
果子⌂44 分钟前
LVS+Keepalived高可用群集
网络·智能路由器·lvs
刘俊辉个人博客1 小时前
端口安全配置示例
运维·网络·数据库·计算机网络·安全·网络安全
猪猪b。1 小时前
dhcp 配置IP实战演练!!!!!
运维·服务器·网络
真实的菜1 小时前
适配器模式:接口转换的神奇魔法[特殊字符],让不兼容的类和谐共处!
java·适配器模式
骚戴2 小时前
SpringBoot源码解析(十五):spring-boot-autoconfigure.jar的模块化设计
java
YuTaoShao2 小时前
Java八股文——计算机网络「应用层篇」
java·网络·计算机网络
Mryan20052 小时前
Android 应用多语言与系统语言偏好设置指南
android·java·国际化·android-studio·多语言
创小匠3 小时前
创客匠人深度:创始人 IP 在小红书的内容策略与私域沉淀方法论
大数据·网络·tcp/ip