文章目录
前言
这篇文章描述的是关于java Aqs框架的整体个人总结,从数据结构,lock接口的角度出发
一、什么是AQS
AbstractQueuedSynchronizer
是大部分java并发工具的基本框架,提供了acquire(获取锁),release(释放锁)的方法。
它的核心思想:
如果请求的资源是空闲的,那就将当前请求的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果共享资源被暂用,就需要一定的阻塞等待机制来保证锁的分配。
二、AQS的基本结构
1.内部子类node
Node prev(用于保存前继节点),
Node tail(用于保存后续节点),还有两个Node分别标记当前是共享锁还是独占锁。
Thread thread(保存当前节点关联的线程地址)。
int waitStatus(用来表示当前节点的状态)。
1.1 节点状态的值
1:线程已取消调度
0:初始话状态,未锁定
-1:后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL
-2:结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,
等待获取同步锁
-3:共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
2 父类的主要属性
int state(用来控制整体可重入的情况)。
Node head;
Node tail;
还有各种int类型的偏移量:
3 小结
从数据结构看出来,它内部是一个双向链表,初始化的时候会在先初始化一个头节点和一个尾节点,两节点的指针互相引用。
很多资料都说它最终维护的是一个队列,那从哪些地方能看出来他维护的是一个队列呢。
三、从ReentrantLock学习AQS
1. ReentrantLock中的内部类含义
1.Sync 继承AbstractQueuedSynchronizer,内部默认实现的是非公平锁的加锁方法。
2.NonfairSync继承Sync,代表非公平锁,加锁方式调用的是Sync的加锁方法。
3.FairSync继承Sync,代表公平锁,加锁方式是自己实现的公平锁的加锁方式
2. ReentrantLock非公平锁的加锁流程
内部执行Sync的lock方法,本质上是调用aqs的acquire方法,在调用子类的tryAcquire方法通过state判断该重入锁的状态,通过cas修改state值,修改成功后,当前线程成功获取到锁,将当前线程设置为独占线程。修改失败,获取一个新的state值,若state值小于0,获取锁失败,新建节点并放入队列中。
流程图大致如下:
3. ReentrantLock公平锁加锁流程
大致流程与非公平锁一样,但是会判断尝试获取锁的节点是否是当前队列的有效结点(一般来说是头结点的后面一个),如果是,才会获得锁
4. ReentrantLock解锁流程
从源码角度可以看出:
内部执行Sync的release方法,该方法是继承AQS,而在release中会调用子类实现的tryRelase方法,释放锁过程中并不会区分公平锁和非公平锁。 释放成功后,找到队列中下一个节点状态<=0的节点,并将其unpark。
流程图如下:
5. 获取锁失败,是如何加入等待队列的呢?
通过当前线程和锁模式创建一个节点,将node的prev指向tail,在通过cas将队列中尾结点的指针指向node。
6. 小结
从上述流程可以知道,因为每次插入节点,都是从尾结点的前一个index插入因为尾结点是虚节点,释放锁也是将有效节点变为虚节点(head节点),所以他维护的是一个先进先出的队列。
四、总结
java中用到aqs的并发工具类还有很多比如CountDownLatch和CyclicBarrier;他们的加锁和释放锁的流程 都是围绕着节点的waitStatus进行的,而对应调用的方法都是AQS的tryAcquire和tryRelease,当然AQS也支持共享锁,其对应的api也是tryAcquireShared和tryReleaseShared。
当然,这篇文章并不是所有AQS的知识点,只是博主认为较为重要的罢了。
五、提问
AQS用的是哪种设计模式?
答:模板方法模式