前面的文章我们讨论了Java并发工具框架基类AbstractQueuedSynchronizer的核心功能和设计思想,本篇在结合源码来分析下相关的内容
先来回顾下AbstractQueuedSynchronizer类功能的一些特点:
(1)支持独占和共享的加锁模式
(2)支持可中断,非阻塞,可超时加锁操作
(3)支持公平和非公平的调度
(4)提供了一些监控能力,比如当前排队锁的个数
(5)核心是基于CLH队列改良的双端链表
AQS将上面的这些功能统一做了抽象和封装,下面我们从源码角度看下相关的知识,首先AQS里有两个内部类,一个是成员内部类ConditionObject其实现了Condition接口,主要功能是与特定的锁关联,并可以控制一组等待线程,比如在前面基于Lock实现的生产者和消费者队列功能,我们就是通过Lock.newCondition()实例,做到细化的控制,可以精确选择通知唤醒生产者线程队列,还是消费者线程队列,老版本的Object.wait,notify,notifyAll是没办法细化控制的。这个不再细说大家知道其功能即可。
另外一个是静态内部类的链表Node,这个类比较重要,其组成如下:
//当前线程使用共享锁
static final Node SHARED = new Node();
//当前线程使用独占锁
static final Node EXCLUSIVE = null;
//当前节点取消锁
static final int CANCELLED = 1;
//当前节点释放同步状态之后,会通知后继节点运行
static final int SIGNAL = -1;
//条件等待。表明当前节点等待在 Condition 上
static final int CONDITION = -2;
//表示releaseShared需要被传播给后续节点(仅在共享模式下使用
static final int PROPAGATE = -3;
/**
* 常量值:
* SIGNAL,
* CANCELLED,
* CONDITION,
* PROPAGATE,
* 0 代表表示当前节点在队列中等待获取锁
*
* 初始情况下,waitStatus = 0
*/
volatile int waitStatus;
//前驱节点原生CLH队列使用
volatile Node prev;
//后继节点(改造)
volatile Node next;
//当前线程
volatile Thread thread;
//表示条件队列中的后继结点
Node nextWaiter;
/**
* 判断节点是否是共享节点
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
//返回当前结点的前继结点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null) {
throw new NullPointerException();
} else {
return p;
}
}
//构造器1
Node() {}
//构造器2, 默认用这个构造器
Node(Thread thread, Node mode) {
//注意持有模式是赋值给nextWaiter
this.nextWaiter = mode;
this.thread = thread;
}
//构造器3, 只在条件队列中用到
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
接着我们看下AQS类里面的关键定义:
//队列的头节点
private transient volatile Node head;
//队列的尾部节点
private transient volatile Node tail;
//同步的状态
private volatile int state;
//竞争时间小于1000纳秒就自旋等待,大于的话就使用LockSupport.park()进入等待阻塞状态
static final long spinForTimeoutThreshold = 1000L;
ok,有了上面的内容,一个双向链表结构的队列结构就具备了,原生的CLH队列是单向链表,如需详细了解可参考我之前的文章。下面我们看下AQS类比较重要的方法:
(1)关于同步的状态的控制
protected final int getState()//获取同步状态
protected final void setState(int newState) //设置新的同步状态
protected final boolean compareAndSetState(int expect, int update) //CAS方式更新同步状态
上面这三个方法可以说是非常关键的,整个队列里面锁控制全靠这个状态字段搞定。 仅仅在读写锁实现里面,这个int类型的32位,被用来表示了两种锁的状态, 分别是写锁的数量(低16位)和共享读锁的数量(高16位) ,所以读写锁支持的最大值是2的16次方-1=65535,当前应该是满足各种需要的,Doug Lea大神在其AQS论文提到当前32是比较合适的,以后可能会扩展成64位的long类型。
(2)acquire方法
public final void acquire(int arg)
public final void acquireShared(int arg)
acquire方法主要用来申请加锁的,AQS内部主要支持两种独占获取和共享获取,当然子类在实现时还会区分公平和非公平获取,如果是公平模式,在锁被占用的情况下会直接追加到队列的尾部,按顺序执行,如果是非公平模式,则直接通过compareAndSetState方法使用CAS加锁,成功就直接使用,失败就追加到队列尾部。
当然这个方法还有非阻塞获取,可中断和可超时调用,这个不再细说,大家知道即可。
(3)release方法
public final boolean release(int arg)
public final boolean releaseShared(int arg)
同acquire方法一样,这里对应也有两个释放锁的方法,分别对应独占模式和共享模式。这里需要注意的是同步state字段不仅仅代表是否有锁占用,还能代表在重入状态下重入的次数,每次加锁都必须对应解锁操作,否则程序就有可能出现死锁问题。
(4)其他对于CLH队列的操作如入队,出队等不再详细介绍,这里主要理解CLH队列的原理
(5)此外还有一些监控的方法不再细说
总结:
本篇文章主要介绍了AbstractQueuedSynchronizer同步框架的一些具体实现及其支持的主要功能,通过对AQS核心的源码简单的剖析,我们就能够发现重点在CLH队列的操作,AQS抽象了同步框架所需全部功能和方法,所以才构成了其他一些同步框架的基础,了解AQS框架的设计和实现能够帮助我们更加容易的学习和使用其他的一些并发工具包。