目录
在了解了java内置锁(synchronized)之后,我们来了解下自1.5之后java在java.util.concurrent包中提供的并发工具类,大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这个行为的抽象就是基于AbstractQueuedSynchronizer简称AQS,AQS定义了一
套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器。
AQS具有以下特性:
- 阻塞等待队列
- 共享/独占
- 公平/非公平
- 可重入
- 允许中断
同时在AQS中维持了两种队列,同步队列和条件队列。
同步等待队列
同步等待队列AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。队列结构如下:
条件等待队列
Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一
起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤
醒,从而重新争夺锁。需要注意的是,在条件队列中的节点的状态都是独占模式。条件等待队列的结构如下:
AQS通过这两种队列实现了线程的调度和唤醒。
AQS主要属性及主要方法
在大致了解了AQS的特性及对应的队列我们来了解下AQS类内部的主要属性和方法。
AbstractQueuedSynchronizer是为与java.util.concurrent包下的一个抽象类,其继承路径如下图所示:
从图中我们可以看到,其继承自AbstractOwnableSynchronizer并实现了Serializable接口,那么我们先来看下AbstractOwnableSynchronizer类的实现:
这是AbstractOwnableSynchronizer类的结构,我们可以看到在该类中定义了一个exclusiveOwnerThread属性,通过名称我们大致了解就是独占线程,同时提供了对应的getter和setter方法,从这儿可以知道AQS是通过这种方式记录占有锁的。接下来我们再来查看AbstractQueuedSynchronizer类的结构,由于该类的机构较复杂我们拿出主要的属性和方法讲解:
-
state 属性:
private volatile int state;
之前提到,AQS是可重入的,那么重入的状态就是通过state这个属性记录的,当线程没进入一次锁,则state会加1,当没释放一次锁state会减一,当state为0则证明完全释放锁。
-
head和tail
private transient volatile Node head; private transient volatile Node tail;
这两个属性就是实现同步等待队列的,head为队列的头节点,tail为队列的尾节点,Node为AbstractQueuedSynchronizer的一个内部类,在该Node节点中维护了prev和next指针用于指向队列的前置节点和后置节点,同时也维护了一个thread属性用于指向当前节点代表的线程,需要注意的是在该队列中,当队列中有数据时,head指向的节点不带表任何线程,就是一个空节点,假设现现在同步队列中有三个节点分别代表线程1、线程2和线程3,那么队列结构如下:
-
判断当前线程是否独占资源
protected boolean isHeldExclusively()
-
独占方式。尝试获取资源,成功则返回true,失败则返回false。
protected boolean tryAcquire(int arg)
-
独占方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryRelease(int arg)
-
共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected int tryAcquireShared(int arg)
-
共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
protected boolean tryReleaseShared(int arg)
以上方法均抛出异常,需要其子类去实现从而决定其子类是共享锁还是独占锁。
那么下一篇来梳理一下AQS的源码,大致思路是先梳理相关类的每个方法,然后再通过一个切入点(ReentrantLock的lock方法)从而将整个流程串起来,个人觉得这种方法对整个源码流程可以有更好的把握。