JAVA并发编程梳理与学习七(AQS)

一、AQS(AbstractQueuedSynchronizer)定义及作用
同步队列器,可以用来构建锁或者其他同步组件,它使用volatitle定义了一个int型的同步变量表示同步状态,通过内置的FIFO队列来完成竞争资源线程的调度工作。这个是并发大神Doug Lea设计的
二、AQS使用思路
我们看AbstractQueuedSynchronizer方法,它是一个抽象类,自己本身并没有实现任何同步方法,里面只是定义了一些获取、改变同步状态来给自定义开发同步组件的开发者使用,既可以实现独占式锁(ReenTrantLock),也可以实现共享锁(ReenTrantReadWriteLock)。
既然是一个抽象类,我们要使用它就只能继承,我们观察ReenTrantLock源码发现ReenTrantLock并没有直接继承AQS,而是通过一个抽象静态内部类Sync来继承AQS并实现AQS内部方法来管理同步状态state,通过getState()、setState(int newState)、compareAndSetState(int expect, int update)来管理同步状态。
为什么Doug Lea大师这么设计呢?为什么不直接继承儿通过内部类来继承呢?
我们可以这么理解大师的设计:
锁是面向使用者的,它定义了使用者与锁的交互,比如我们线程中使用,直接new就行了,隐藏了实现细节。
AQS同步器面向的是实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的等待、排队、唤醒等底层操作。
锁和同步器很好的隔离了使用者和实现者所关注的领域
实现者需要继承同步器并重写指定方法,随后将同步器放到自己定义的同步组件(比如:锁)中,然后调用同步器提供的模板方法,这些模板方法会调用使用者重写的方法。
三、AQS中体现的设计模式中–模板方法模式
模板方法模式:模板方法,按我们日常理解,比如我们写简历有一个简历模板,定义好一个框架,然后每个人在模板上根据自己的情况写自己的技术栈、自己的经历啥的。模板方法就是如此设计的,在父类中定义一个操作中的算法骨架,而将一些步骤的实现延迟到子类中,可以使子类不改变算法结构即可重新定义该算法的特定步骤。我们最常见的就是Spring框架里的各种Template
我们把AQS提供模板方法分类
在这里插入图片描述
下面是可重写的
在这里插入图片描述
访问或者修改同步状态的方法
getState():获取同步状态
setState():设置当前同步状态
compareAndSetState(int expect,int update):使用CAS设置同步状态,该方法能保证状态设定的原子性
四、AQS源码分析
1.AQS数据结构–节点和同步队列
(1)节点note
AQS是CLH队列锁的一种变体实现,既然是队列锁就要有一个数据结构来存储一些需要用到的数据,比如节点的状态、前驱节点等,这个数据结构就是AQS中的静态内部类Note.
我们可以看看源码里面的数据结构,看看是怎么样的
在这里插入图片描述
注意:前驱和后继节点,比如我们排队自己打饭,我们必须知道我们前面的是谁,这样他打完饭后才能把勺子交给我们,我们也必须知道我们后面是谁,这样我们打完饭后才能知道把勺子交给谁
过程:当线程获取同步状态失败时,同步器会将当前线程、等待状态等构造成一个节点Node并加入同步队列中,并阻塞当前线程,当同步状态释放时,会把队列中首节点的线程唤醒,使其再次获取同步状态。
Node:保存的使获取同步状态失败线程的引用、等待状态、前驱和后继节点
(2)首节点head和尾节点tail
AQS还有一个首节点head执行队列头部,尾节点tail指向队列尾部。我们看看源码发现,首节点是不保存线程信息的节点。
。
在这里插入图片描述
2.AQS源码分析
我们用Lock主要用lock方法和unlock方法,我们重点看这2个方法的源码,我们以ReentrantLock为例,ReentrantLock默认是非公平锁,我们先以非公平锁为例子。我们前面说过
(1)在这里插入图片描述
(2)我们进入方法,先进入tryAcquire这个方法,点击进去tryAcquire方法,我们发现AQS下面是一个空实现,上面我们也说过这个是个模板方法,需要我们自己去自己锁里面去重写,我们看看ReentrantLock是如何重写的,点进入发现进入nonfairTryAcquire这个方法,我们进入nonfairTryAcquire这个方法看看
在这里插入图片描述
(3)我们再回到acquire方法,tryAcquire(arg)这里返回false,会进入 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法,我们先看addWaiter(Node.EXCLUSIVE), arg),进入addWaiter方法
在这里插入图片描述
下面是enq方法
在这里插入图片描述
(4)addWaiter(Node.EXCLUSIVE), arg)方法就是把当前线程打包成一个节点,通过自旋CAS加入队列,让他成为队列的尾部,下面我们来看acquireQueued方法
在这里插入图片描述(5)上面未获得锁得线程已经处于阻塞状态,下面我们看如何解除阻塞状体,重新去竞争锁,我们先看unlock方法
在这里插入图片描述进入release方法首先看到得是tryRelease(arg),我们上面说了tryRelease(arg)方法也是AQS得模板方法,我们去ReentrantLock看如何重写的
在这里插入图片描述
下面回到release方法
在这里插入图片描述
我们再看看unparkSuccessor是如何唤醒下一个节点的
在这里插入图片描述
(6)唤醒下一个线程后,会重新进入(4)中的自旋死循环去重新拿锁。
上面是独占式的非公平锁的源码及实现过程,我们总结一下:
在这里插入图片描述
其他非公平锁、共享锁都差不多,不过重写方法不同,有兴趣可以去自己看看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章