相信大部分人知道AQS是因为ReentrantLock,ReentrantLock的底层是使用AQS来实现的。还有一部分人知道共享锁(Semaphore/CountDownLatch/CyclicBarrier)也是由AQS来实现的。也就是说AQS中有独占和共享两种模式。但你以为这就是AQS的全部了吗?其实不然。AQS中还有第三种模式:条件队列。像Java中的阻塞队列(ArrayBlockingQueue、LinkedBlockingQueue等)就是由AQS中的条件队列来实现的。而上面所说的独占模式和共享模式是由AQS中的CLH队列来实现的。
所以本系列的AQS源码分析将会分为三篇文章来推送(独占模式/共享模式/条件队列),并且会深入到每一行源码进行分析,希望能给你带来一个全面的AQS认识。那么首先本篇文章就来分析一下AQS中独占模式的实现吧。
1 简介AQS全称AbstractQueuedSynchronizer,是一个能多线程访问共享资源的同步器框架。作为Doug Lea大神设计出来的又一款优秀的并发框架,AQS的出现使得Java中终于可以有一个通用的并发处理机制。并且可以通过继承它,实现其中的方法,以此来实现想要的独占模式或共享模式,抑或是阻塞队列也可以通过AQS来很简单地实现出来。
一些常用的并发工具类底层都是通过继承AQS来实现的,比如:ReentrantLock、Semaphore、CountDownLatch、ArrayBlockingQueue等(这些工具类也都是Doug Lea写的)。
Doug Lea是我们学习Java并发框架绕不开的神级人物,以下是他个人的履历
纽约州立大学奥斯威戈分校的计算机科学教授,现任计算机科学系主任,他专门研究并发编程和并发数据结构的设计。他是Java Community Process执行委员会的成员,JSR 166的主席, 美国计算机协会会士,欧洲计算机领域重量级奖项达尔-尼加德奖得主
信息来自wiki百科:https://en.wikipedia.org/wiki/Doug_Lea
扯回正题,AQS中有几个重要的概念:
state:用来记录可重入锁的上锁次数;
exclusiveOwnerThread:AQS继承了AbstractOwnableSynchronizer,而其中有个属性exclusiveOwnerThread,用来记录当前独占锁的线程是谁;
CLH同步队列:FIFO双向链表队列,此CLH队列是原CLH的变种,由原来的不断自旋改为了阻塞机制。队列中有头节点和尾节点两个指针,尾节点就是指向最后一个节点,而头节点为了便于判断,永远指向一个空节点,之后才是第一个有数据的节点;
条件队列:能够使某些线程一起等待某个条件具备时,才会被唤醒,唤醒后会被放到CLH队列中重新争夺锁资源。
AQS定义资源的访问方式有两种:
独占模式:只有一个线程能够获取锁,如ReentrantLock;
共享模式:多个线程可以同时获取到锁,如Semaphore、CountDownLatch和CyclicBarrier。
AQS中使用到了模板方法模式,提供了一些方法供子类来实现,子类只需要实现这些方法即可,至于具体的队列的维护就不需要关心了,AQS已经实现好了。
1.1 Node上面所说的CLH队列和条件队列的节点都是AQS的一个内部类Node构造的,其中定义了一些节点的属性:
1 static final class Node { 2 /** 3 * 标记节点为共享模式 4 */ 5 static final Node SHARED = new Node(); 6 /** 7 * 标记节点为独占模式 8 */ 9 static final Node EXCLUSIVE = null; 10 /** 11 * 标记节点是取消状态,CLH队列中等待超时或者被中断的线程,需要从CLH队列中去掉 12 */ 13 static final int CANCELLED = 1; 14 /** 15 * 该状态比较特殊,如果该节点的下一个节点是阻塞状态,则该节点处于SIGNAL状态 16 * 所以该状态表示的是下一个节点是否是阻塞状态,而不是表示的本节点的状态 17 */ 18 static final int SIGNAL = -1; 19 /** 20 * 该状态的节点会被放在条件队列中 21 */ 22 static final int CONDITION = -2; 23 /** 24 * 用在共享模式中,表示节点是可以唤醒传播的。CLH队列此时不需要等待前一个节点释放锁之后,该节点再获取锁 25 * 共享模式下所有处于该状态的节点都可以获取到锁,而这个传播唤醒的动作就是通过标记为PROPAGATE状态来实现 26 */ 27 static final int PROPAGATE = -3; 28 /** 29 * 记录当前节点的状态,除了上述四种状态外,还有一个初始状态0 30 */ 31 volatile int waitStatus; 32 /** 33 * CLH队列中用来表示前一个节点 34 */ 35 volatile Node prev; 36 /** 37 * CLH队列中用来表示后一个节点 38 */ 39 volatile Node next; 40 /** 41 * 用来记录当前被阻塞的线程 42 */ 43 volatile Thread thread; 44 /** 45 * 条件队列中用来表示下一个节点 46 */ 47 Node nextWaiter; 48 49 //... 50 } 1.2 CLH队列