啥?小胖连公平锁 & 非公平锁都不知道?真的菜!(上)

简介: 啥?小胖连公平锁 & 非公平锁都不知道?真的菜!

公平锁 & 非公平锁


来到多线程的第十二篇,前十一篇请点文末底部的上、下一篇标签,这篇说说什么是公平锁 & 非公平锁?开篇之前,先聊聊它们的定义以及优缺点。


「公平锁」:多个线程按顺序排队获取锁,每个线程获取机会均等,但永远只有队列首位线程能获取到锁。


  • 优点:每个线程等待一段时间后,都有执行的机会,不至于出现某个线程饿死在队列中。


  • 缺点:是队列里面除了第一个线程,其他的线程都会阻塞,cpu 唤醒阻塞线程的开销会很大。


「非公平锁」:多个线程(不管是不是队列首位)去获取锁时会尝试直接获取锁,能获取到就执行任务,否则乖乖排队。


  • 优点:获取锁更加灵活、吞吐量大、减少 CPU 唤醒线程的开销。


  • 缺点:会出现某些线程永远获取不到锁,饿死在队列中,最终由 JVM 回收。


说了这么一堆,大家也可能看得不是很明白。狗哥就举个在大学跟室友一起排队买早饭的场景。是这样的,帅得一批的狗哥(也就是我啦),跟室友小钊(渣男)、小宝以及小民去买早饭。狗哥以及室友们都看做是一个线程。


「首先是公平锁」


某天狗哥起的最晚,到了饭堂。室友们都已经在排队买早饭了,作为良好市民的狗哥自然也是乖乖过去排队。这样大家都准守秩序,先到先得,很公平。


640.png


「然后是非公平锁」


还是狗哥起的最晚,到了食堂刚好小宝买完去上徐国保老师的课了。小钊这比还在思考今晚帮哪个妹子修电脑。狗哥不讲武德,趁机插了上去队头,见到是我插队,后面的小钊、小民即使不爽也不敢说啥,只能看着我买早餐。这就是非公平锁插队成功的例子。


640.png


但是,偶尔也有不成功的时候。比如小钊这比思考完要帮妹子阿毛修电脑,这时我想插上去被他发现使出一记闪电五连鞭把我给赶走了,狗哥也是欺软怕硬,只能乖乖去后面排队。这就是非公平锁插队失败的例子。


640.png


看完了这个例子是不是对非公平锁 & 公平锁这对 CP 的理解清晰了很多?刚看例子,不讲代码的行为无耻至极。下面随狗哥来读读源码。


源码分析


「如何使用?」


ReentrantLock lock = new ReentrantLock();


上面的代码熟悉么?其实,大家平时应该有使用过 ReentrantLock 的话就已经使用过非公平锁(ReentrantLock 默认)了。源码如下所示:


640.png


想要使用公平锁,创建锁的时候直接给个 true 即可。


ReentrantLock lock = new ReentrantLock(true);


「具体是怎么实现的?」


源码可以看到 ReentrantLock 内部有一个 Sync 内部类。他继承了 AbstractQueuedSynchronizer (也就是我们常说的 AQS),在操作锁的时候大多都是通过 Sync 实现的。


public class ReentrantLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = 7373984872572414699 L;
        /** Synchronizer providing all implementation mechanics */
        private final Sync sync;


abstract static class Sync extends AbstractQueuedSynchronizer {
    ...
}


下图得知,Sync 他又有两个子类,分别是 NonfairSync(非公平锁) & FairSync(公平锁),见名知义。


640.png


「非公平锁加锁实现」


从 nonfairTryAcquire 方法内部的 compareAndSetState 方法可以看到,非公平锁获取锁主要是通过 CAS 方式,修改 state 状态为 1,并且通过 setExclusiveOwnerThread (current) 把当前持有锁的线程改为自己。


static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691 L;
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 获取锁的关键代码
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}


从上面我们知道 AbstractQueuedSynchronizer (AQS) 是 ReentrantLock  加解锁的核心,这样说可能有些笼统,还是刚刚的场景,狗哥给你们画几个图理解下。


1、首先狗哥因为长得帅,线程狗哥一进来就 CAS 判断 state == 0 有戏,把自己设置为加锁线程,成功获取到锁并买到早饭了。


640.png


2、这时小钊这逼过来想插队,但一判断 state 发现是 1,有人已经持有锁了。于是只能灰溜溜的滚回去排队了。


640.png


3、狗哥买完早饭,把 state 设置为 0 并释放锁,唤醒小钊。


640.png


4、就在唤醒期间(小钊还没醒的时刻)烫了个原谅绿藻头的小宝不讲武德插队,先行 CAS 判断 state == 0,可以设置自己为加锁线程。这时小钊可算醒了,CAS 判断 state,发现是 1,这尼玛。狗子不是说到我了吗?怎么又被人占用了,于是又灰溜溜的去排队。


640.png


以上就是非公平锁的加锁过程,上面提到非公平锁有部分线程可能会饿死,看完大家也可能理解了。小钊就是那个饿死的线程。。。


「公平锁加锁实现」


公平锁跟非公平锁加锁的逻辑差不多,唯一就是公平加锁的 if 判断中多了 hasQueuedPredecessors 是否队首的判断。


static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540 L;
    final void lock() {
        acquire(1);
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 多了 hasQueuedPredecessors 是否队首判断
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}


这点从以下源码可以看出,就算看不懂。注释也写得很清楚。


640.png



相关文章
|
8月前
|
机器学习/深度学习 人工智能 自然语言处理
3步,0代码!一键部署DeepSeek-V3、DeepSeek-R1
3步,0代码!一键部署DeepSeek-V3、DeepSeek-R1
291 0
|
网络协议 安全 网络安全
常见的网络传输协议有几种
常见的网络传输协议涵盖多个层次,包括传输层(如TCP、UDP、SCTP)、应用层(如HTTP/HTTPS、FTP、SMTP、DNS、SSH)、网络层(如IP、ICMP、ARP)、数据链路层(如Ethernet、PPP、Wi-Fi)及安全协议(如SSL/TLS、IPSec)。这些协议各具特色,适用于不同场景,如TCP用于可靠传输,UDP适合实时应用,而HTTP/HTTPS则服务于网页浏览和数据交换。通过这些协议的协同工作,现代互联网和局域网得以实现多样化的应用和服务。
Openlayers+vue调用GeoServer的api报跨域错误解决方法
Openlayers+vue调用GeoServer的api报跨域错误解决方法
396 0
|
消息中间件 安全 小程序
短信服务怎么使用API/SDK发送短信
如何快速使用阿里云OpenAPI开发者门户或阿里云SDK完成常见操作,例如添加短信签名、添加短信模板、发送短信服务和查询短信发送详情等
短信服务怎么使用API/SDK发送短信
|
机器学习/深度学习 传感器 算法
【灰色预测】基于粒子群算法优化灰色预测模型GM(1,1)实现数据预测附matlab代码
【灰色预测】基于粒子群算法优化灰色预测模型GM(1,1)实现数据预测附matlab代码
|
2天前
|
弹性计算 运维 搜索推荐
三翼鸟携手阿里云ECS g9i:智慧家庭场景的效能革命与未来生活新范式
三翼鸟是海尔智家旗下全球首个智慧家庭场景品牌,致力于提供覆盖衣、食、住、娱的一站式全场景解决方案。截至2025年,服务近1亿家庭,连接设备超5000万台。面对高并发、低延迟与稳定性挑战,全面升级为阿里云ECS g9i实例,实现连接能力提升40%、故障率下降90%、响应速度提升至120ms以内,成本降低20%,推动智慧家庭体验全面跃迁。
|
3天前
|
数据采集 人工智能 自然语言处理
3分钟采集134篇AI文章!深度解析如何通过云无影AgentBay实现25倍并发 + LlamaIndex智能推荐
结合阿里云无影 AgentBay 云端并发采集与 LlamaIndex 智能分析,3分钟高效抓取134篇 AI Agent 文章,实现 AI 推荐、智能问答与知识沉淀,打造从数据获取到价值提炼的完整闭环。
351 91
|
10天前
|
人工智能 自然语言处理 前端开发
Qoder全栈开发实战指南:开启AI驱动的下一代编程范式
Qoder是阿里巴巴于2025年发布的AI编程平台,首创“智能代理式编程”,支持自然语言驱动的全栈开发。通过仓库级理解、多智能体协同与云端沙箱执行,实现从需求到上线的端到端自动化,大幅提升研发效率,重塑程序员角色,引领AI原生开发新范式。
854 156