【转载】AQS这样学就很简单了

hello,小伙伴们好,我是江湖人送外号[道格牙]的子牙老师。

AQS,抽象队列同步器,为了解决Java多线程环境下的互斥与同步而生。JUC包中的很多类都是基于AQS实现的,可见AQS的重要性。但是很多同学在学AQS的过程中觉得很难很抽象,在这里分享下我是如何精通AQS的。

精通AQS需要结合线程互斥、线程同步两套场景去理解,本篇文章主要讲AQS针对互斥场景是如何处理的。

如果你是道格李

如果你是道格李,你要实现一套机制来保证线程互斥,你会如何实现呢?你肯定不会一上来就写代码对吧,你会想有哪些场景会出现线程互斥、针对每个场景抽象出需要实现的功能、针对这些功能底层选择什么样的数据结构什么算法……

这其实是一种非常好的学习方法,回归到问题本身去思考问题,然后带着问题去找答案,而不是一上来就一头扎进代码中。

核心功能

如果你要实现一套机制保证线程互斥,核心是实现这六个功能:抢锁、释放锁、入队、出队、阻塞、唤醒。下面分别说下这六个功能如何实现以及在实现的时候需要考虑哪些特殊情况。

抢锁

如果当前没有任何线程持有锁,那进来的线程就可以去抢锁。

因为存在多个线程同时上锁,所以需要通过机制来保证同一时刻只能有一个线程能够操作锁标志位,这个机制就是CAS。

如果有线程持有了锁,这时候进来的线程如果是持有锁的线程,就发生了重入,需要记录重入次数,因为解锁的时候也要解锁相应的次数;如果不是持有锁的线程,那有两个选择:让线程运行结束、让线程阻塞等其他线程用完锁再唤醒运行。

显然,让线程运行结束不合适,因为线程该执行的任务还未执行。正确的做法肯定是先把这个线程保存下来,然后让线程阻塞。待持有锁的线程运行结束再唤醒执行。这里就是公平锁与非公平锁的唯一差异所在,一个临界点。

释放锁

持有锁的线程将该执行的任务执行完就得释放锁。当然肯定不是这么阶段,还得考虑锁重入、线程唤醒。

入队

未抢到锁且不是重入的线程就需要被阻塞等待唤醒,那唤醒的时候去哪找这个线程呢?很明显这些线程满足先阻塞先唤醒原则,与数据结构队列的FIFO特性相吻合。所以在实现的时候需要借助队列,将这部分线程封装成节点入队。

入队就得考虑从头部入队还是从尾部入队。如果队列中还没有数据,队列的头尾两个节点都指向这个节点。如果队列中有数据,就从尾部插入。

大家在看AQS源码的时候会发现AQS不是这样做的,它会多一个节点。AQS队列的头结点永远是当前持有锁的线程占用,为什么要这样做呢?这就需要对CPU内部结构及工作机制有深入理解,感兴趣的同学可以深入学习下:CLHLock、MCSLock。

出队

线程对应的节点何时出队呢?肯定是唤醒以后。那由谁来操作出队呢?是唤醒你的线程还是你自己?可想而知,由自己来操作更合理。那自己什么时候操作出队呢?肯定是唤醒以后。只有理解了这个逻辑,你才能看得懂AQS中阻塞那块的代码为什么那样写。

正常的情况下出队都是从头部出,但是有特殊情况会移除队列中部的节点或尾部节点。

阻塞

线程如果没有抢到锁依然在那尝试抢锁这就是所谓的自旋锁,很显然,这样很浪费资源,肯定没有抢到锁的线程执行完任务唤醒高效。那如何不占用资源呢?就是阻塞自己,让出资源,不被调度。

唤醒

未抢到锁的线程为了不占用资源阻塞了自己,拿到锁的线程执行完任务需要来唤醒,不然就会出现奇怪的现象:抢到锁的线程执行完任务退出了,未抢到锁的线程全部阻塞在那里等待唤醒。

如果你现在想问了解这些有什么用?我的答案是这些是思想,了解了这些才能看得懂AQS的源码。

AQS三大核心机制

别看AQS代码挺多,其实搞懂这三个机制,代码理解起来就非常easy。

state

这个属性就是锁标志位。如果有线程抢到了锁,这个值就会通过CAS改成1。何时改回来呢?持有锁的线程执行完任务释放锁的时候。

waitStatus

这个属性有五个值:0,-1,-2,-3,1。这里介绍其中三个。其他的值跟互斥没关系,我会在讲到相关的技术时讲到。这几个值直接的转换后面会写文章细致,本篇文章大致讲下。

未抢到锁的线程被封装成Node插入队列,这个属性默认值为0。

插入队列后会进行两次自旋,如果都没抢到锁,就会将它的前置节点的waitStatus改为-1。注意一下这里,改的不是自己的waitStatus,而是它前置节点的。为什么要这样做呢?这就是自旋锁算法CLH的理论。相当于在它的前置节点上设置了一个闹钟,这样在唤醒的时候就不需要去队列取数据,直接判断自己的该属性就可以了。

如果是队列中的第二个节点但是抢锁失败了,这个时候就将自己的waitStatus设置为1。这样的线程就得不到调度机会了,会被其他线程从队列中移除。

队列

如果想看懂AQS源码,必须对队列的相关操作算法非常熟悉,比如初始化、判空、入队、出队……建议在看AQS源码之前自己用Java实现一遍队列。

结语

这篇文章虽然没有讲任何AQS的源码,但是如果你真的看懂了这篇文章,你去读读AQS的源码,你会发现读起来非常轻松。

推荐阅读

1、AQS中的cancelAcquire何时会运行

2、数据在计算机中是如何存储的

3、能够走到聚光灯下的“码师”,都做对了什么

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值