剖析基于并发AQS的共享锁的实现(基于信号量Semaphore)
深入剖析基于并发AQS的(独占锁)重入锁(ReetrantLock)及其Condition实现原理
转自 https://www.jianshu.com/p/99f1c01f3226
很详细!
1、J.U.C 之 AQS
1.1 介绍
AbstractQueuedSynchronizer类(抽象队列同步器),简称AQS,一个用来构建锁
和同步器
的框架 。从JDK1.5开始,引入了并发包,也就是J.U.C,大大提高了JAVA程序的并发性能,而AQS则是J.U.C的核心
,是并发类中核心部分,它提供一个基于FIFO队列
,这个队列可以构建锁或其他相关的同步装置的基础框架。
AQS底层数据结构:
底层采用双向链表
,是队列
的一种实现,因此可以当做是一个队列。其中Sync queue
即同步队列
,它是双向链表
,包括head结点
(主要用作后续的调度)与tail结点
。Condition queue不是必须的
,单向链表
,只有在需要使用到condition
的时候才会存在这个单向链表,并且可能存在多个
Condition queue
AQS的模板方法有:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
1.2 设计思路
-
使用
Node
实现FIFO
队列,可以用于构建锁或者其他同步装置的基础框架 -
利用了一个
int类型
表示state状态
。在AQS中,存在一个state成员变量,基于AQS有一个同步组件ReentrantLock
,在这个组件中,state
表示获取锁的线程数
,假如state = = 0 表示无线程获取锁,state == 1表示已有线程获取锁,state > 1 表示锁的数量
-
使用方法是
继承
。AQS的设计是基于模板方法
,使用需要继承AQS
,并覆写其中的方法
。 -
子类通过继承并通过实现它的方法管理其状态{
acquire() 和 release()
}的方法操纵状态
-
可以同时实现
排它锁
和共享锁
模式(独占、共享)。它的所有子类中,要么实现并使用它的独占功能API,要么实现共享锁的功能,而不会同时
使用两套API。即便是它比较有名的子类ReentrantReadWirteLock
也是通过两个内部类 读锁
和写锁
分别使用两套API实现的。AQS在功能上,有独占控制和共享控制两种功能。 -
在LOCK包中的相关锁(常用的有
ReentrantLock
、ReadWriteLock
)都是基于AQS来构建.然而这些锁都没有直接来继承AQS
,而是定义了一个Sync类去继承AQS
,因为锁
面向的是使用用户
,而同步器
面向的则是线程控制
,那么在锁的实现中聚合同步器而不是直接继承AQS就可以很好的隔离二者所关注的事情.
基于以上设计,AQS具体实现的大致思路:
AQS内部维护了一个CLH队列
来管理锁,线程首先会
尝试获取锁,如果失败,会将当前线程
以及等待状态等信息
包装成Node
结点加入同步队列
(Sync queue)中。接着通过CAS不断循环尝试获取锁
,条件是
当前结点为head直接后继
才会尝试,如果失败则会阻塞自己
,直到自己被唤醒
;而当持有锁的线程,释放锁的时候,会唤醒队列中后继线程
。基于这些基础的设计和思路,JDK提供了许多基于AQS的子类。
独占式锁过程总结:
AQS的模板方法acquire
通过调用子类
自定义实现的tryAcquire
获取同步状态失败后 --> 将线程构造成Node节点
(创建一个独占式节点 )(addWaiter) --> 将Node节点添加到同步队列对尾
(addWaiter) --> 前驱节点 是头节点
的节点才会以自旋
的方式尝试获取锁
(acquirQueued)。如果该节点的前驱不是头节点
或者 该节点的前驱节点是头节点单获取同步状态失败
,则判断当前线程需要阻塞
,如果需要阻塞则需要被唤醒过后才返回。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒 头节点的 后继节点
。
共享式锁过程总结:
共享式获取与独占式获取的最主要区别在于
同一时刻
能否有多个线程同时获取
到同步状态。通过调用acquireShared(int arg)
方法可以共享式得获取同步状态。
同步器调用tryAcquireShared(int arg)方法尝试获取同步状态,其返回值为int类型,当返回值大于0
时,表示能够获取
同步状态。因此,在共享式获取的自旋过程中,成功获取同步状态并且退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0。共享式释放
同步状态状态是通过调用releaseShared(int arg)
方法
CountDownLatch
、ReentrantReadWriteLock
、Semaphore
等都是共享式
获取同步状态的。
之后未整理