什么是AQS框架
1995年sun公司公布了第一个java语言版本号,能够说从jdk1.1到jdk1.4期间java的使用主要是在移动应用和中小型企业应用中,在此类领域中基本不用设计大型并发场景,当然也没有大型互联网公司使用java,由于操心它本身的性能。在互联网及server硬件迅猛的发展下,sun公司更加注重企业级应用方面,毫无疑问高并发是一个重要的主题,于是在J2SE5.0(jdk1.5)代号为老虎的版本号中添加了更加强大的并发相关的操作包——java.util.concurrent。
此后java在高并发中表现优异,非常多大型互联网公司都使用java作为主要开发语言。比如阿里巴巴、ebay等,这些公司系统的訪问绝对是属于世界级的大型并发场景。反映了java在大型并发场景是可行的。Jdk的并发包提供了各种锁及同步机制,事实上现的核心类是AbstractQueuedSynchronizer。我们简称为AQS框架,它为不同场景提供了实现锁及同步机制的基本框架,为同步状态的原子性管理、线程的堵塞、线程的解除堵塞及排队管理提供了一种通用的机制。
Jdk的并发包(juc)的作者是Doug Lea。但当中思想却是结合了多位大师的智慧。假设你想深入理解juc的相关理论能够參考Doug Lea写的《The_java.util.concurrent_Synchronizer_Framework》论文。从这里能够找到AQS的理论基础,包括框架的基本原理、需求、设计、实现思路、使用方法及性能,由于这些方面篇幅较大。本文不打算涉及全部方面,主要将针对AQS类的结构及相关操作进行分析。
ASQ将线程封装到一个Node里面,并维护一个CHL Node FIFO队列。它是一个非堵塞的FIFO队列,也就是说在并发条件下往此队列做插入或移除操作不会堵塞,是通过自旋锁和CAS保证节点插入和移除的原子性,实现无锁高速插入。
事实上AbstractQueuedSynchronizer主要就是维护了一个state属性、一个FIFO队列和线程的堵塞与解除堵塞操作。
state表示同步状态。它的类型为32位整型。对state的更新必须要保证原子性。这里的队列是一个双向链表,每一个节点里面都有一个prev和next,它们各自是前一个节点和后一个节点的引用。须要注意的是此双向链表除了链头其它每一个节点内部都包括一个线程,而链头能够理解为一个空节点。
图2-5-5-1
对于队列的结构我们须要深入理解下,图2-5-5-2展示的是组成双向链表当中一个节点的结构,该节点包括五个主要元素,表示的意思例如以下表,
图2-5-5-2
属性 | 含义 |
Node prev | 前驱节点,指向前一个节点 |
Node next | 兴许节点,指向后一个节点 |
Node nextWaiter | 用于存储condition队列的兴许节点 |
Thread thread | 入队列时的当前线程 |
int waitStatus | 有五种状态: ① SIGNAL,值为-1,表示当前节点的兴许节点中的线程通过park被堵塞了,当前节点在释放或取消时要通过unpark解除它的堵塞。 ② CANCELLED。值为1,表示当前节点的线程由于超时或中断被取消了。 ③ CONDITION,值为-2。表示当前节点在condition队列中。 ④ PROPAGATE。值为-3。共享模式的头结点可能处于此状态,表示无条件往下传播,引入此状态是为了优化锁竞争。使队列中线程有序地一个一个唤醒。 ⑤ 0,除了以上四种状态的第五种状态,通常是节点初始状态。 |
前驱节点prev的引入主要是为了完毕超时及取消语义。前驱节点取消后仅仅需向前找到一个未取消的前驱节点就可以;兴许节点的引入主要是为了优化兴许节点的查找,避免每次从尾部向前查找;nextWaiter用于表示condition队列的兴许节点。此时prev和next属性将不再使用。并且节点状态处于Node.CONDITION; waitStatus表示的是兴许节点状态。这是由于AQS中使用CLH队列实现线程的结构管理。而CLH结构正是用前一节点某一属性表示当前节点的状态。这样更easy实现取消和超时功能。
上面是对节点及节点组成队列的结构的介绍,接着介绍AQS相关的一些操作。包括锁的获取与释放、队列的管理、同步状态的更新、线程堵塞与唤醒、取消中断与超时中断等等。