AQS官方解读
AQS 只是一个框架,具体资源的获取/释放方式交由自定义同步器去实现,AQS 这里只定义了一个接口,具体资源的获取交由自定义同步器去实现了
提供了一个框架用来实现阻塞的锁和相关的同步器(比如信号量,事件等等),他们依赖于FIFO的等待队列(相对于synchronized, 它在底层其实也是对应两个集合:WaitSet,EntrySet,而AQS只是它又做了一些扩展,等待队列有多个,而阻塞队列只有一个)。该类被设计成用于为大多数依赖单个int值表示状态的同步器提供有用的基础(其中提到了一个int值,这个值的含义非常的丰富,不同的场景下它代表的意思也是截然不同的,后面会说)。子类必须定义改变此状态的受保护方法,以及根据该对象被获取或释放来定义该状态的含义
其中它定义的受保护的方法,而且它里面都是直接抛异常了,很明显具体类是必须要定义这些受保护的方法的,为啥?因为抽象的父类是没法实现这些方法,因为具体的场景需要依赖于子类。
image.png
image.png
鉴于这些,给定这些,这个类中的其他方法执行所有排队和阻塞机制。子类可以维护其他的状态字段,但是仅跟踪使用getState(),setState(int) 和 compareAndSetState(int, int)方法操纵的以原子方式更新的int值进行同步
总的来说,它是提供了一个底层的基础组件,至于它到底是在什么场景下有什么样的表述形式是由具体子类来实现的。
AQS抽象类核心抽象方法
定义了若干个protected的重要核心方法,而这些受保护的方法它具体逻辑的执行应该取决于它的子类它所处理的具体业务的场景以及子类所代表的具体的业务的含义
里面的实现直接抛出异常了,很明显就是子类是一定要来实现它的
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
1、尝试以独占模式获取。 该方法应该查询对象的状态是否允许以独占模式获取,如果是,则获取它。
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
2、尝试去设置AQS中的state用来反映在排它模式下的一次释放,此方法经常是被线程调用执行锁的释放
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
3、尝试以共享模式获取。 该方法应该查询对象的状态是否允许在共享模式下获取该对象,如果是这样,就可以获取它。
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
4、尝试去设置AQS中的state用来反映在共享模式下的一次释放,此方法经常是被线程调用执行锁的释放
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
5、如果同步锁以独占的方式被当前调用者线程所持有则返回true
如果是在独占模式下,必须实现tryAcquire()和tryRelease()方法;
如果是在共享模式下,必须实现tryAcquireShared()和tryReleaseShared()方法
AQS结构图
image.pngAQS中有两个内部类
- 1、Node内部类:封装了AQS在受阻塞的线程,AQS会把当前受阻塞的线程包装成一个Node对象,使得线程之间形成一个FIFO的双向队列
- 2、ConditionObject内部类:该类实现了Condition 和 java.io.Serializable(序列化)两个类,可以直接看成是Condition类只是加了个序列化而已,每一个Condition对象都维护着一个条件队列,而sychronized的wait()底层只有一个等待集合
流程
- 1、将FIFO看成Synchronized的EntrySet,condition看成是WaitSet,只是synchronized只有一个等待集合WaitSet,而AQS有多个等待集合condition。通过多个Condition来将这些条件队列中的线程进行分文别类的处理,这也是AQS相对Synchronized的改进
- 2、对于某个线程调用了某个condition的await()方法时,就会进入相应的条件队列中。当调用了对应的condition的condition.signal()或者condition.signalAll()时,线程会进入到FIFO的阻塞队列当中,由阻塞队列来判断当前资源是否可以获取到,如果可以获取到的话线程就会正常的执行,而如果获取不到的话依然处于FIFO阻塞队列中
3、论AQS类中的state变量
image.png在AQS当中有一个重要的成员变量来决定当前的线程是否有能力能够拿到AQS执行的资源,在不同的场景下其state的含义是不一样的,下面举两个场景,先有个大概印象:
-
1、ReentrantLock:典型的排它锁,也就是只要有一个线程调用的lock()方法其它线程就不可能进入lock()方法中,此时state表示线程可重入的次数,因为ReentrantLock是典型的可重入锁,也就是lock()里面的代码还可以调用lock()方法,此时state值就不断的加1,而调用unlock()时则state会减1,一直减到0表示此锁彻底得到释放了,此时其它线程就可以拿到执行资源了。
-
2、ReentrantReadWriteLock:读写锁,而不管读锁还是写锁本质上都是依赖于一个AQS对象,那么如何通过一个int的state变量来表示两种场景呢?此时用的是state的高16位表示的是读线程的数量,而低16位表示的是写线程的数量了。
关于这个变量是AQS设计最为精妙的地方,该字段的含义得从它的子类进行了解。也就是说当调用了AQS中的某一个condition.await()之后得依据这个state变量来决定线程是否能执行AQS的资源,如果不能则就会进入FIFO里面进行阻塞等待了。
4、AQS在Java并发包的应用规律
在子类中,该子类运用了AQS的机制,该子类中都定义了一个Sync内部类,该类是继承于AbstractQueuedSynchronizer,然后具体由它来实现AQS的细节
可重入锁
image.png
读写锁
image.png
5、Node的结构,以及Node在FIFO阻塞队列和ConditionObject等待队列的使用方式
Node的结构
static final class Node {
static final Node SHARED = new Node();//共享锁结点
static final Node EXCLUSIVE = null;//排他锁结点
volatile Node prev;//使用在FIFO阻塞队列中指向前驱结点
volatile Node next;//使用在FIFO阻塞队列中指向后继结点
Node nextWaiter;//使用在ConditionObject中指向下一个结点
volatile Thread thread;//当前线程
}
从源码可以说明,ConditionObject等待队列是使用单向链表实现的,而FIFO阻塞队列是使用双向链表实现的
(1)FIFO阻塞队列添加元素时,使用的是Node的prev和next实现前后指针
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
(2)ConditionObject等待队列添加元素时,使用的是Node的nextWaiter实现后继指针
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
6、关于AQS与synchronized关键字之间的关系
1、synchronized关键字在底层C++实现中,存在两个重要的数据结构(集合):WaitSet,EntryList,都是用环形双向链表构造成的
2、WaitSet中存放的是调用了Object的wait方法的线程对象(被封装成C++的Node对象)
3、EntryList中存放的是陷入到阻塞状态,需要获取monitor的那些线程对象
4、当一个线程被notify后,它就会从WaitSet中移动到EntryList中
5、进入到EntryList后,该线程依然需要与其他线程争抢monitor对象
6、如果争抢到,就表示该线程获取到了对象的锁,它就可以排他方式执行对应的同步代码块
1、AQS中存在两种队列,分别是Condition对象上的条件队列,以及AQS本身的阻塞队列
2、这两个队列中的每一个对象都是Node实例(封装了线程对象)
3、当位于Condition条件队列中的线程被其他线程singal后,分两种情况,ReentrantLock 和 ReentrantReadWriteLock 同时都有公平锁与非公平锁两种情况,具体看ReentrantLock 和 ReentrantReadWriteLock两篇文章
4、AQS的FIFO阻塞队列本质上是由一个双向链表构成的,Condition的等待队列本质上是由一个单向链表构成的,
5、在获取AQS锁时,这些进入到FIFO阻塞队列中的线程会按照在队列中的排序先后获取
6、当AQS阻塞队列中的线程获取到锁后,就表示该线程已经可以正常执行了
7、陷入到阻塞状态的线程,依然需要进入到操作系统的内核态,进入阻塞(park方法实现)
网友评论