018:从零手写互斥锁:深入理解AQS核心原理与实战实现

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论

🔥🔥🔥(源码获取 + 调试运行 + 问题答疑)🔥🔥🔥  有兴趣可以联系我

🔥🔥🔥  文末有往期免费源码,直接领取获取(无删减,无套路)

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。

🔥🔥🔥(免费,无删减,无套路):java swing管理系统源码 程序 代码 图形界面(11套)」
链接:https://pan.quark.cn/s/784a0d377810
提取码:见文章末尾
🔥🔥🔥(免费,无删减,无套路): Python源代码+开发文档说明(23套)」
链接:https://pan.quark.cn/s/1d351abbd11c
提取码:见文章末尾

🔥🔥🔥(免费,无删减,无套路):计算机专业精选源码+论文(26套)」
链接:https://pan.quark.cn/s/8682a41d0097
提取码:见文章末尾
🔥🔥🔥(免费,无删减,无套路):Java web项目源码整合开发ssm(30套)
链接:https://pan.quark.cn/s/1c6e0826cbfd
提取码:见文章末尾

🔥🔥🔥(免费,无删减,无套路):「在线考试系统源码(含搭建教程)」

链接:https://pan.quark.cn/s/96c4f00fdb43
提取码:见文章末尾


前言

在多线程编程中,锁是保证线程安全的重要工具。Java提供了丰富的锁机制,其中AbstractQueuedSynchronizer(AQS)作为并发包的核心基础框架,支撑了ReentrantLock、CountDownLatch等众多同步器的实现。今天,我将带领大家深入AQS内部,从零开始实现一个简单的互斥锁,通过实战来理解AQS的工作原理。

AQS核心原理回顾

在开始编码之前,我们先简要回顾AQS的核心机制。AQS通过一个FIFO队列来管理获取锁失败的线程,并通过一个volatile的state变量来表示同步状态。其核心思想是:

  • 当线程尝试获取锁时,如果state表示锁可用,则获取成功

  • 如果锁已被占用,则将当前线程封装为Node节点加入队列等待

  • 当前驱节点释放锁时,会唤醒后继节点重新尝试获取

简单互斥锁的实现

下面我们基于AQS实现一个最简单的不可重入互斥锁Mutex:

 import java.util.concurrent.locks.AbstractQueuedSynchronizer;
 import java.util.concurrent.locks.Lock;
 ​
 public class Mutex implements Lock {
     
     // 内部同步器,继承AQS
     private static class Sync extends AbstractQueuedSynchronizer {
         
         // 尝试获取锁:state为0表示未锁定,1表示已锁定
         @Override
         protected boolean tryAcquire(int acquires) {
             // 使用CAS操作尝试将state从0改为1
             if (compareAndSetState(0, 1)) {
                 // 成功设置state为1,表示获取锁成功
                 // 设置当前线程为独占所有者
                 setExclusiveOwnerThread(Thread.currentThread());
                 return true;
             }
             return false;
         }
         
         // 尝试释放锁
         @Override
         protected boolean tryRelease(int releases) {
             // 检查当前线程是否持有锁
             if (getState() == 0 || !isHeldExclusively()) {
                 throw new IllegalMonitorStateException();
             }
             // 清除独占所有者
             setExclusiveOwnerThread(null);
             // 将state设置为0,不需要CAS因为只有持有者能释放
             setState(0);
             return true;
         }
         
         // 判断当前线程是否独占资源
         @Override
         protected boolean isHeldExclusively() {
             return getExclusiveOwnerThread() == Thread.currentThread();
         }
     }
     
     private final Sync sync = new Sync();
     
     @Override
     public void lock() {
         sync.acquire(1);
     }
     
     @Override
     public void unlock() {
         sync.release(1);
     }
     
     // 其他Lock接口方法的简单实现...
     @Override
     public boolean tryLock() {
         return sync.tryAcquire(1);
     }
     
     @Override
     public void lockInterruptibly() throws InterruptedException {
         sync.acquireInterruptibly(1);
     }
     
     @Override
     public boolean tryLock(long timeout, java.util.concurrent.TimeUnit unit) 
             throws InterruptedException {
         return sync.tryAcquireNanos(1, unit.toNanos(timeout));
     }
 }

核心方法深度解析

tryAcquire方法

tryAcquire是获取锁的核心方法,其实现体现了AQS的精妙之处:

 protected boolean tryAcquire(int acquires) {
     if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
     }
     return false;
 }

这里使用了CAS(Compare-And-Swap)操作,这是一个原子操作,确保在多线程环境下只有一个线程能够成功将state从0改为1。CAS的原子性是通过底层CPU指令保证的,是现代并发编程的基石。

tryRelease方法

tryRelease方法负责释放锁:

 protected boolean tryRelease(int releases) {
     if (getState() == 0 || !isHeldExclusively()) {
         throw new IllegalMonitorStateException();
     }
     setExclusiveOwnerThread(null);
     setState(0);
     return true;
 }

注意这里释放锁时不需要使用CAS操作,因为只有持有锁的线程才能调用释放方法,不存在竞争条件。

Mutex工作流程详解

让我们通过一个时序图来理解Mutex的完整工作流程:

与ReentrantLock的对比

我们实现的简单Mutex与JDK中的ReentrantLock相比,缺少了几个重要特性:

1. 可重入性

ReentrantLock支持同一个线程多次获取同一把锁,内部通过记录重入次数来实现。而我们的Mutex如果同一个线程连续调用两次lock()会导致死锁。

2. 公平性选项

ReentrantLock提供了公平锁和非公平锁两种模式:

  • 公平锁:按照线程请求的顺序分配锁

  • 非公平锁:允许插队,可能提高吞吐量

我们的Mutex实现的是非公平锁。

3. 条件变量支持

ReentrantLock可以创建多个Condition对象,实现精细的线程等待/通知机制。

4. 中断处理

ReentrantLock提供了更完善的中断处理机制。

性能优化思考

在实际生产环境中,我们可以基于这个基础版本进行多种优化:

自旋优化

在加入等待队列前,可以让线程短暂自旋,避免线程切换的开销:

 // 简化的自旋尝试
 for (int i = 0; i < MAX_SPINS; i++) {
     if (tryAcquire(1)) {
         return;
     }
 }
 // 自旋失败后加入队列

状态预测

可以根据历史数据预测锁的持有时间,决定是否直接入队还是先自旋。

实战应用场景

虽然我们的Mutex功能简单,但在特定场景下仍有应用价值:

  1. 教学演示:清晰地展示锁的基本原理

  2. 性能测试基准:作为对比基准测试更复杂锁的性能

  3. 定制化需求:在某些需要极简实现的场景中使用

总结

通过实现这个简单的Mutex,我们深入理解了AQS的核心工作机制:

  1. AQS通过state变量管理同步状态

  2. CAS操作保证了状态变更的原子性

  3. CLH队列管理等待线程,避免忙等待

  4. 模板方法模式让使用者只需关注核心逻辑

这个实战例子不仅帮助我们理解了Java并发包的底层原理,更重要的是培养了分析复杂系统时"由简入繁"的思维方式。在后续的学习中,我们可以基于这个基础,逐步理解更复杂的同步器实现。

理解AQS不仅对使用Java并发工具有帮助,更重要的是它提供了一种设计高并发组件的思路和模式,这种思想可以应用到各种编程语言和系统的设计中。

扩展思考

  1. 如何在这个基础上实现可重入功能?

  2. 如何实现公平锁版本?

  3. 如何添加条件变量支持?

  4. 在分布式环境中,如何实现类似的互斥锁?

这些问题留给读者进一步思考和探索,希望本文能为你的并发编程之旅提供一个坚实的起点。


往期免费源码对应视频:

免费获取--SpringBoot+Vue宠物商城网站系统

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥  有兴趣可以联系我

💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕网址:扣棣编程
🎉感谢支持常陪伴,
🔥点赞关注别忘记!

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值