背景了解:
继续学习Java并发,这次学习一个全新的并发组件----AbstractQueuedSynchronizer(抽象队列同步器),俗称AQS:
它是一个抽象类,然后大致看一下它的具体实现类:
是不是常见的一些类都是它的实现者,也可见它的重要性,大致瞅一眼它的实现源码行数:
相当的复杂呀,然而从某种程度来说其实它又没那么的复杂:如果你理解了它背后原理,它要解决什么问题,在不同场景下不同的字段分别代表啥含义。而且在之前Java并发所学的很多内容在AQS中或多或少都有所体现,比如之前学习的synchronized关键字在JVM的底层实现https://www.cnblogs.com/webor2006/p/11441679.html,以及它在C++底层的实现细节https://www.cnblogs.com/webor2006/p/11442551.html;还有对于Object中的wait(),notify(),notifyAll();还有对于Condition的await(),signal(),singalAll()。之前所打下坚实的基础也有利于AQS的学习,因为本质上AQS跟synchronized的相似度基本能达到80%以上,只不过AQS在synchronized基础之上进行了一定的扩充,典型的:我们知道synchronized它是一种独占锁(如果有一个线程拿到了Monitor,其它线程不管是读还是写都无法获取该锁了,只有获取锁的线程将其释放掉其它新的线程才有获取的可能),所以在有些场景它是有些受限的,而AQS在synchronized的基础之上通过一些子类实现实现了读写锁的分离(我们知道如果只是读的话上锁其实是没有必要的,所以读取是共享锁,而对于写锁则是排它锁),下面则开始AQS的学习之旅,这次先对它有一个全局的认识既可,随着之后再慢慢了解它的细节。
AQS官方解读:
先来从权威的注释文档来了解一下它是干嘛,官方注释非常的多,读读开头部分先对它有个基础认识:
说到"wait queue"等待队列,又回想起synchronized, 它在底层其实也是对应两个集合:
而AQS只是它又做了一些扩展,等待队列有多个,而阻塞队列只有一个。接下来继续往下读:
其中提到了一个int值,这个值的含义非常的丰富,不同的场景下它代表的意思也是截然不同的,之后再说,继续:
其中它定义的受保护的方法:
而且它里面都是直接抛异常了:
很明显具体类是必须要定义这些受保护的方法的,为啥?因为抽象的父类是没法实现这些方法,因为具体的场景需要依赖于子类。
总的来说,它是提供了一个底层的基础组件,至于它到底是在什么场景下有什么样的表述形式是由具体子类来实现的。
AQS结构整体了解:
AQS结构图解浅析:
接下来则来了解一下它里面定义的结构:
接下来看一下它里面定义了两个内部类:
看一下它们的结构:
所以对于AQS而言它是严重依赖于Condition的,而如之前所学,一个Condition的产生是这样来的:
从这就能隐约体会到跟sychronized关键字的区别了,对于同一个Lock是可以生成多个Condition,每一个Condition对象都维护着一个条件队列,而sychronized的wait()底层只有一个等待集合,下面再来看一下Node的定义:
实际上Node就封装了AQS在受阻塞的线程,AQS会把当前受阻塞的线程包装成一个Node对象,使得线程之间形成一个FIFO的双向队列:
根据这俩内部类的结构,对于AQS其实它大概的形态是这么个结构【很重要,认识它有助于后来的学习】,画画图:
这个在前面也已经说过了,由于一个Lock可以产生多个Condition对象,所以就是上图那样了,而每一个Condition里面都维护着一个等待队列,所以此时的形态就变为:
接着它还依赖于双向队列的Node,所以:
处在这个FIFO队列中的线程就是处于一个被阻塞的状态,当AQS中某一个条件变量可用的话按公平的原则第一个线程就会获得相关的资源去执行了,这里就涉及到从条件队列中转换到FIFO的阻塞队列的一个过程,这个过程就可以假想一下当时synchronized关键字,当调用对象的wait()方法时就会进入到它底层的waitSet当中:
而它就相当于AQS的条件队列,只是wait()调用之后就立马会进入到waitset当中,不会有条件的区分,而在AQS中针对这部分做了很好的改进,它可以通过多个Condition来将这些条件队列中的线程进行分文别类的处理,比如说调用了第二个condition.await()就会进入第二个条件队列中,但是其原理基本上是一样的。当调用了condition.signalAll()线程并非立马能得到执行,而是此时就会进入到FIFO的阻塞队列当中,由阻塞队列来判断当前资源是否可以获取到,如果可以获取到的话线程就会正常的执行,而如果获取不到的话依然处于阻塞队列中,而这个阻塞队列是不是类似于synchronized底层的它:
再论state变量:
而在AQS当中有一个重要的成员变量来决定当前的线程是否有能力能够拿到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里面进行阻塞等待了。
AQS在Java并发包的应用规律初探:
其实在Java并发包中使用AQS的类都有一个规律,先来看一下互斥锁ReentrantLock:
而这个Sync看一下它的结构:
再来看一个:
这次对于AQS有一个宏观的了解既可,之后再不断深入。
关注个人公众号,获得实时推送