AQS揭秘:为什么它既不是公平锁也不是非公平锁?

你真的了解AQS吗?

在Java并发编程的世界里,AQS(AbstractQueuedSynchronizer)就像一位"幕后英雄",默默支撑着ReentrantLock、CountDownLatch等众多并发工具的运作。但你有没有想过:AQS本身到底是公平锁还是非公平锁?

答案可能会让你惊讶------AQS两者都不是!它更像是一个"万能工具箱",提供了构建锁和同步器的基础零件,而具体是组装成公平锁还是非公平锁,则完全取决于使用者(也就是基于AQS的子类)的选择。

AQS的"中立"智慧:把选择权交给你

AQS的设计哲学非常精妙:它只负责最基础的工作(比如维护线程等待队列、管理同步状态),而把关键的决策逻辑(比如谁能获得锁、什么时候获得锁)通过"钩子方法"留给子类来实现。

想象一下,AQS就像一家提供场地和基础设备的咖啡馆,而子类(如ReentrantLock)则是具体的经营者------可以选择做成安静的书店咖啡吧(公平锁),也可以做成热闹的快节奏咖啡店(非公平锁),AQS并不干涉具体的经营策略。

关键的"钩子"在哪里?

AQS通过以下几个核心方法将公平性的决定权交给子类:

  • tryAcquire:尝试获取锁的逻辑
  • tryRelease:尝试释放锁的逻辑 正是这些方法的实现,决定了最终是公平还是非公平的行为。

实例:ReentrantLock如何选择公平性

ReentrantLock是AQS最典型的应用,它通过构造函数的fair参数,轻松实现了两种锁模式。

非公平锁:"先到先得?我先试试插队!"

ReentrantLock默认是非公平锁,就像排队买奶茶时总有人想先问问"还需要等多久"------新线程会先"插队"尝试获取锁,成功了就直接拿走,失败了才乖乖去排队。

java 复制代码
// 非公平锁的获取逻辑
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 不管队列有没有人,先CAS抢一波再说
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // ...
    return false;
}

公平锁:"排队是基本礼仪"

当fair参数为true时,ReentrantLock就变成了公平锁------新线程会先看看队伍里有没有人,有人就自觉排队,没人再尝试获取锁。

java 复制代码
// 公平锁的获取逻辑
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 先检查队列,没人排队才尝试获取
        if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) 
        {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // ...
    return false;
}

关键区别就在于hasQueuedPredecessors()方法------它会检查是否有线程在排队,就像在问"我前面还有人吗?"

为什么AQS要这样设计?

AQS的这种"中立"设计,体现了Java并发框架的高明之处:

  • 不做一刀切:不同场景对公平性的需求不同,AQS不强制选择
  • 性能与公平的平衡:非公平锁吞吐量更高,公平锁保证顺序,用户可以按需选择
  • 灵活性至上:同一个AQS框架,既能实现ReentrantLock,也能实现CountDownLatch等完全不同的同步器

一句话总结

AQS就像一张白纸,本身没有公平或非公平的属性,而是由使用者(子类)通过实现特定方法来描绘出不同的"公平性图景"。理解了这一点,你就真正抓住了Java并发编程的精髓!

希望这篇文章能帮你更轻松地理解AQS的设计智慧。如果觉得有收获,别忘了点赞收藏哦

相关推荐
Funcy17 分钟前
XxlJob 源码分析03:执行器启动流程
后端
豌豆花下猫2 小时前
Python 潮流周刊#118:Python 异步为何不够流行?(摘要)
后端·python·ai
秋难降2 小时前
SQL 索引突然 “罢工”?快来看看为什么
数据库·后端·sql
Access开发易登软件3 小时前
Access开发导出PDF的N种姿势,你get了吗?
后端·低代码·pdf·excel·vba·access·access开发
中国胖子风清扬4 小时前
Rust 序列化技术全解析:从基础到实战
开发语言·c++·spring boot·vscode·后端·中间件·rust
bobz9655 小时前
分析 docker.service 和 docker.socket 这两个服务各自的作用
后端
野犬寒鸦5 小时前
力扣hot100:旋转图像(48)(详细图解以及核心思路剖析)
java·数据结构·后端·算法·leetcode
phiilo5 小时前
golang 设置进程退出时kill所有子进程
后端
花花无缺5 小时前
python自动化-pytest-用例发现规则和要求
后端·python
程序员小假5 小时前
我们来说一说 Cglib 与 JDK 动态代理
后端