(三)AbstractQueuedSynchronizer(AQS)的底层原理的实现?

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36520235/article/details/81263037

AQS到底是个什么东西?他能用来干嘛?他的实现原理是怎样的?

1. 首先AQS是一个抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架(其实说白了,就是说当有多个线程时,为了更好的控制并发,可以把AQS看成是一个正在管理挂号买东西排队的管理员,由AQS来进行对这个排队的人员(也就是把线程看成节点)进行判断排队的人有没有权利去买东西)

2. 他其实不是说可以单独来实现一个什么功能,但是有好多重要的功能都是得依靠他的底层支持,才能实现其他的ReentrantLock/Semaphore/CountDownLatch…各种锁机制功能

3. 先不考虑代码,直接先理一下AQS的整个流程大概是个什么样的,心里有点逼数,想看源码,百度一堆这样的源码。

整体流程图
这里写图片描述
核心部分图
这里写图片描述

实现原理的大概具体步骤如下:

前提须知:

1、首先你要知道的是AQS的锁的类型是分为两种的:第一种是独占式锁的获取和释放(),第二中的是共享式的锁的释放和获取

2、第一步首先是用acquire(int arg)方法先拿到这个线程的共享资源的状态,这个状态变量是用volatile来修饰的,只有当获取到的state大于等于0时才表示获取锁成功(重入一次锁状态就加1,释放一次锁状态就减一),若果失败就把当前线程包装成一个node节点入队列(FIFO)

3、第二步就是第一步获取状态小于0的时候说明失败,然后调用的 addWaiter(Node mode)方法把该线程包装成一个节点,为了提搞性能,首先执行一次快速入队操作,即直接尝试将新节点加入队尾

4、然后再上一步的基础上,如果尝试把该节点加入队列尾部失败,这里又会去调用 enq(final Node node)方法,(1)先去判断这个队列是不是已经初始化了,如果初始化了就用CAS保证只有一个头结点可以初始化成功,如果没有初始化县初始化再CAS来保证只有一个线程节点创建成功,(2)最后在enq方法中进行无限次的自旋,直到,如果成功会直接使用CAS操作只能返回一个节点(compareAndSetTail(pred, node))

5、操作完了上面的操作之后,就相当于是线程节点已经成功的加入的等待队列中,然后进行的就是挂起当前线程,等待被唤醒。然后调用boolean acquireQueued(final Node node, int arg) ,先把锁的标记为默认为false,然后去判断该节点的前置结点是不是头结点,如果是把当前结点线程用setHead(node);设置为头结点(这里有个小坑,就是只有head头结点才是cpu正在执行的线程节点,后面的节点都是等待线程节点,而且在这个头结点执行的线程过程中头结点是可以中断的),如果设置头结点成功,就把锁标记为设置为true并返回,

6、然后再接着上一个步骤继续判断,如果没有获取锁成功,则进入挂起逻辑,也就是如果没有成功的话就进入下一个方法,node是当前线程的节点,pred是它的前置节点
boolean shouldParkAfterFailedAcquire(Node pred, Node node),在这个方法判断成功之后,会继续接着5的步骤把锁的标记设置为true,然后如果判断锁的标记是与否,否的话继续 cancelAcquire(node);

7、处理获取锁失败之后的挂起逻辑boolean shouldParkAfterFailedAcquire(Node pred, Node node)的方法,(1)前置节点的waitStatus是Node.SIGNAL则返回true,然后会执行parkAndCheckInterrupt()方法进行挂起,(2)如果前置节点的waitStatus大于0的话,把当前结点赋给前置结点的下一个结点,如果不大于0 的话,使用CAS的compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 最后挂起的状态改为false,它是用来判断当前节点是否可以被挂起,也就是唤醒条件是否已经具备,即如果挂起了,那一定是可以由其他线程来唤醒的。该方法如果返回false,即挂起条件没有完备,那就会重新执行acquireQueued方法的循环体,进行重新判断,如果返回true,那就表示万事俱备

8、这里继续执行步骤5的 void cancelAcquire(Node node) 方法,拿到当前失败线程节点的等待状态是不是小于0,大于的话直接Node.CANCELLED;赋值给正在遍历的线程节点的waitStatus 中,然后继续判断当前节点是不是尾节点,是的话使用CAS操作compareAndSetNext(pred, predNext, null);把节点设置为空,若不是尾节点的话,当大于0的时候跳出循环,继续如果当前节点的后继节点没有被取消就把前置节点跟后置节点进行连接,相当于删除了当前节点compareAndSetNext(pred, predNext, next);

9、最后是释放锁,先去过去当前节点的waitStatus,然后如果waitStatus小于0尝试去释放锁使用compareAndSetWaitStatus(node, ws, 0)CAS操作,然后去判断如果当前线程节点的下一个节点,如果发现节点的waitStatus 小于0,就说明找到了待唤醒的节点,然后不为空的时候,就去唤醒该节点。

总结:

展开阅读全文

没有更多推荐了,返回首页