1995年sun公司发布了第一个Java语言版本,可以说从JDK1.1到JDK1.4期间Java的使用主要是在移动应用和中小型企业应用中。在此类领域中基本不会涉及大型并发场景,当然也没有大型互联网公司使用Java,因为担心它本身的性能。
在互联网及服务器硬件迅猛的发展下,sun公司开始更加注重企业级应用方面,毫无疑问高并发是一个主题。于是在J2SE5.0(JDK1.5)代号为老虎的版本中增加了更加强大的并发工具包——java.util.concurrent。此后Java在高并发中表现优异,很多大型互联网公司都使用Java作为主要开发语言。例如阿里巴巴、ebay等,这些公司系统的访问绝对属于世界级的大型并发场景,这也反映了Java在大型并发场景是可行的。
01
同步器
当业务涉及到多个线程操作数据时就需要考虑并发操作问题,比如并发对银行卡账户进行操作,如果没考虑同步问题可能就会在业务上引来很多问题。同步器是专门为多线程并发同步机制而设计。同步器的定义为:多线程并发执行时线程之间通过某种共享状态来实现同步,只有当状态满足某条件时才能触发线程往下执行。在不同的应用场景中,对同步器的需求也各种各样。按照设计模式的思想,人们把这些不同的同步器抽象成用一个统一的同步框架作为基础,然后通过继承方式将此同步框架通过模板来规范子类的实现。这个同步框架就是AQS,本文将对AQS框架进行介绍。
02
AQS框架
JDK的并发包提供了各种锁及同步机制,其实现的核心类是AbstractQueuedSynchronizer,我们简称为AQS框架。它为不同场景提供了实现锁及同步机制的基本框架,为同步状态的原子性管理、线程的阻塞、线程的解除阻塞及排队管理提供了一种通用的机制。
JDK并发工具包(juc)的作者是DougLea,但其中思想却是结合了多位大师的智慧。如果你想深入理解juc的相关理论可以阅读DougLea的论文《The_java.util.concurrent_Synchronizer_Framework》。从该论文中可以找到AQS的理论基础,包括框架的基本原理、需求、设计、实现思路、用法及性能等等。
03
AQS主体框架
AbstractQueuedSynchronizer框架,简称AQS框架。它将线程封装到一个Node里面,并维护一个CHL Node FIFO队列。它是一个非阻塞的FIFO队列,也就是说在并发条件下往此队列做插入或移除操作不会阻塞。它是通过自旋操作和CAS操作来保证节点插入和移除的原子性,从而实现无锁快速插入。
AQS主要就是维护了一个state属性、一个FIFO队列和线程的阻塞与解除阻塞操作。state表示同步状态,它的类型为32位整型,对state的更新必须要保证原子性。这里的队列是一个双向链表,每个节点里面都有一个prev和next,它们分别是前一个节点和后一个节点的引用。需要注意的是此双向链表除了链头其他每个节点内部都包含一个线程,而链头可以理解为一个空节点。
04
队列结构
对于队列的结构我们需要深入理解下,如图展示的是组成双向链表其中一个节点的结构,该节点包含五个主要元素。
- Node prev:前驱节点,指向前一个节点。
- Node next:后续节点,指向后一个节点。
- Node nextWaiter:用于存储condition队列的后续节点。
- Thread thread:入队列时的当前线程。
- int waitStatus:有五种状态:
- SIGNAL,值为-1,表示当前节点的后续节点中的线程通过park被阻塞了,当前节点在释放或取消时要通过unpark解除它的阻塞。
- CANCELLED,值为1,表示当前节点的线程因为超时或中断被取消了。
- CONDITION,值为-2,表示当前节点在condition队列中。
- PROPAGATE,值为-3,共享模式的头结点可能处于此状态,表示无条件往下传播。假如两条线程同时释放锁,通过竞争其中一条负责唤醒下一节点,而另一条则将头部设置为此状态,新节点唤醒后直接根据头部此状态唤醒下下个节点。
- 值为0,除了以上四种状态的第五种状态,一般是节点初始状态。
05
总结
上面是对JDK内置并发框架AQS的介绍,包括了主体结构、节点及节点队列结构等进行了介绍。接下去的章节会讲解AQS相关的一些操作,包括锁的获取与释放、队列的管理、同步状态的管理、线程阻塞与唤醒、中断的支持、超时与取消等等。
- END -