AQS的核心成员

AQS出于“分离变与不变”的原则,基于模板模式实现。AQS为锁获取、锁释放的排队和出队过程提供了一系列的模板方法。由于JUC的显式锁种类丰富,因此AQS将不同锁的具体操作抽取为钩子方法,供各种锁的子类(或者其内部类)去实现。

状态标志位


AQS 中维持了一个单一的volatile修饰的状态信息state,AQS使用int类型的state 标示锁的状态,可以理解为锁的同步状态。


//同步状态,使用volatile保证线程可见
private volatile int state;

state 因为使用volatile保证了操作的可见性,所以任何线程通过getState()获得状态都可以得到最新值。AQS 提供了getState()、setState()来获取和设置同步状态,具体如下:
//获取同步的状态
protected final int getstate() {
        return state;
}

//设置同步的状态
protected final void setstate(int newstate){
        state = newstate;
}

//通过CAS设置同步的状态
protected final boolean compareAndSetState(int expect, int update){
        return unsafe.compareAndSwapInt(this,stateOffset,expect, update);
}    

由于setState()无法保证原子性,因此AQS给我们提供了compareAndSetState()方法利用底层 UnSafe的 CAS 机制来实现原子性。compareAndSetState()方法实际上调用的是unsafe成员的 compareAndSwapInt()方法。
以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程执行该锁的lock()操作时,会调用tryAcquire()独占该锁并将state加1。此后,其他线程再tryAcquire()时就会失败,直到A线程 unlock()到state=0(释放锁)为止,其他线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state能回到零态。
AbstractQueuedSynchronizer继承了 AbstractOwnableSynchronizer,这个基类只有一个变量叫 exclusiveOwnerThread,表示当前占用该锁的线程,并且提供了相应的get()和set()方法,具体如下:

public abstract class AbstractOwnableSynchronizer
        implements java.io.Serializable {

        //表示当前占用该锁的线程
        private transient Thread exclusiveOwnerThread;

        // 省略 get/set 方法

队列节点类

AQS是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系。节点类型通过内部类 Node 定义,其核心的成员如下:

        static final class Node {

                /**节点等待状态值1:取消状态*/
                static final int CANCELLED = 1;


                /**节点等待状态值-I:标识后继线程处于等待状态*/
                static final int SIGNAL    -1:    
                /**节点等待状态值-2:标识当前线程正在进行条件等待*/

                static final int CONDITION--2;
                /**节点等待状态值-3:标识下一次共享锁的acquireShared操作需要无条件传播*/

                static final int PROPAGATE --3;
                //节点状态:值为SIGNAL、CANCELLED、CONDITION、PROPAGATE、0

                //普通的同步节点的初始值为0,条件等待节点的初始值为CONDITION(-2)

                volatile int waitstatus;

                //节点所对应的线程,为抢锁线程或者条件等待线程

                volatile Thread thread;

                //前驱节点,当前节点会在前驱节点上自旋,循环检查前驱节点的waitstatus 状态
                volatile Node prevs

                //后继节点
                volatile Node next;

                //此属性指向下一个条件等待节点,即其条件队列上的后继节点    

                //若当前Node不是普通节点而是条件等待节点,则节点处于某个条件的等待队列上

                Node nextWaiter;

                ......    

        }

1. waitStatus 属性
每个节点与等待线程关联,每个节点维护一个状态waitStatus,waitStatus的各种值以常量形式进行定义。waitStatus的各常量值具体如下:        
(1) static final int CANCELLED =1
waitStatus值为1时表示该线程节点已释放(超时、中断),已取消的节点不会再阻塞,表示线程因为中断或者等待超时,需要从等待队列中取消等待。    
由于该节点线程等待超时或者被中断,需要从同步队列中取消等待,因此该线程被置1.节点进入了取消状态,该类型节点不会参与竞争,且会一直保持取消状态。
(2) static final int SIGNAL=-1
waitStatus为SIGNAL(-1)时表示其后继的节点处于等待状态,当前节点对应的线程如果释放了同步状态或者被取消,就会通知后继节点,使后继节点的线程得以运行。
(3) static final int CONDITION=-2
waitStatus为-2时,表示该线程在条件队列中阻塞(Condition有使用),表示节点在等待队列中(这里指的是等待在某个锁的CONDITION上,关于CONDITION的原理后面会讲到),当持有锁的线程调用了CONDITION的signal()方法之后,节点会从该CONDITION的等待队列转移到该锁的同步队列上,去竞争锁(注意:这里的同步队列就是我们讲的AQS维护的 FIFO队列,等待队列则是每个 CONDITION关联的队列)。
节点处于等待队列中,节点线程等待在CONDITION上,当其他线程对CONDITION调用了 signal)方法后,该节点从等待队列中转移到同步队列中,加入对同步状态的获取中。
(4) static final int PROPAGATE=-3    
waitStatus为一3时,表示下一个线程获取共享锁后,自己的共享状态会被无条件地传播下去,因为共享锁可能出现同时有N个锁可以用,这时直接让后面的N个节点都来工作。这种状态在CountDownLatch中使用到了。
为什么当一个节点的线程获取共享锁后,要唤醒后继共享节点?共享锁是可以多个线程共有    
获取锁。当一个节点的线程获取共享锁后,必然要通知后继共享节点的线程也可以获取锁了,这样就不会让其他等待的线程等很久,这种向后通知(传播)的目的也是尽快通知其他等待的线程尽快获取锁。    
(5) waitStatus为0
waiStatus为0时,表示当前节点处于初始状态。 Node节点的waitStatus状态为以上5种状态的一种。

2.thread 成员
Node的thread成员用来存放进入AQS队列中的线程引用: Node的nextWaiter成员用来指向自己的后继等待节点,此成员只有线程处于条件等待队列中的时候使用。

3.抢占类型常量标识
Node 节点还定义了两个抢占类型常量标识:SHARED和EXCLUSIVE,具体如下:

static final class Node {
        //标识节点在抢占共享锁
        static final Node SHARED =new Node();

        //标识节点在抢占独占锁
        static final Node EXCLUSIVE = null;

        ......

}

SHARED表示线程是因为获取共享资源时阻塞而被添加到队列中的;EXCLUSIVE 表示线程是因为获取独占资源时阻塞而被添加到队列中的。


FIFO 双向同步队列


        AQS 的内部队列是CLH队列的变种,每当线程通过AQS获取锁失败时,线程将被封装成一个Node节点,通过CAS原子操作插入队列尾部。当有线程释放锁时,AQS会尝试让队头的后继节点占用锁。
        AQS通过内置的 FIFO双向队列来完成线程的排队工作,内部通过节点head和tail记录队首和队尾元素,元素的节点类型为Node类型,具体如下:
        /*首节点的引用*/
        private transient volatile Node head;

        /*尾节点的引用*/
        private transient volatile Node tail;


        AQS的首节点和尾节点都是懒加载的。懒加载的意思是在需要的时候才真正创建。只有在线程竞争失败的情况下,有新线程加入同步队列时,AQS才创建一个head 节点。head 节点只能被 setHead()方法修改,并且节点的waitStatus不能为CANCELLED。尾节点只在有新线程阻塞时才被创建。
        一个包含4个节点的AQS同步队列的基本结构如图所示。

AQS的核心成员_第1张图片

JUC 显式锁与 AQS的关系


        AQS是java.util.concurrent包的一个同步器,它实现了锁的基本抽象功能,支持独占锁与共享锁两种方式。该类是使用模板模式来实现的,成为构建锁和同步器的框架,使用该类可以简单且高效地构造出应用广泛的同步器(或者等待队列)。
        java.util.concurrent.locks 包中的显式锁如ReentrantLock、ReentrantReadWriteLock,线程同步工具如Semaphore,异步回调工具如FutureTask等,内部都使用了AQS作为等待队列。通过开发工具进行 AQS的子类导航会发现大量的AQS子类以内部类的形式使用,具体如图所示。

AQS的核心成员_第2张图片

同样,我们也能继承AQS类去实现自己需求的同步器(或锁)。

ReentrantLock与AQS 的组合关系


        这里以 ReentrantLock为例为大家介绍一下JUC显式锁与AQS的组合关系。


1.ReentrantLock 与AQS的组合关系
ReentrantLock是一个可重入的互斥锁,又称为“可重入独占锁”。ReentrantLock锁在同一个时间点只能被一个线程锁持有,而可重入的意思是,ReentrantLock锁可以被单个线程多次获取。经过观察,ReentrantLock把所有 Lock接口的操作都委派到一个Sync类上,该类继承了 AbstractQucuedSynchronizer:
        static abstract class Sync extends AbstractQueuedSynchronizer {...}


        ReentrantLock为了支持公平锁和非公平锁两种模式,为Sync又定义了两个子类,具体如下:

        final static class NonfairSync extends Sync {...}
        final static class FairSync extends Sync {...}
        NonfairSync为非公平(或者不公平)同步器,FairSync为公平同步器。ReentrantLock 提供了两个构造器,具体如下:
        public ReentrantLock() {    //默认的构造器    
                sync = new NonfairSync();    //内部使用非公平同步器   

        }

        public ReentrantLock (boolean fair) { //true 为公平锁,否则为非公平锁
                sync = fair ? new FairSync() : new NonfairSync();

        }


        ReentrantLock的默认构造器(无参数构造器)被初始化为一个NonfairSync对象,也就是使用非公平同步器。所以,默认情况下ReentrantLock为非公平锁。带参数的构造器可以根据 fair参数的值具体指定ReentrantLock的内部同步器使用FairSync还是NonfairSyne。
由 ReentrantLock的lock(和unlockO的源码可以看到,它们只是分别调用了sync对象的lockO)和 release()方法。
        public void lock(){    //抢占显式锁    
                sync.lock();

        public void unlock () {//释放显式锁
                sync,release(1);
        }
        通过以上委托代码可以看出,ReentrantLock的显式锁操作是委托(或委派)给一个Sync 内部类的实例来完成的。而Sync内部类只是AQS的一个子类,所以本质上ReentrantLock 的显式锁操作是委托(或委派)给AQS完成的。一个ReentrantLock对象的内部一定有一个AQS类型的组合实例,二者之间是组合关系。
        ReentrantLock 的内部结构如图所示。

AQS的核心成员_第3张图片

2.显式锁与 AQS之间的组合关系
组合和聚合比较类似,二者都表示整体和部分之间的关系。
聚合关系的特点是,整体由部分构成,但是整体和部分之间并不是强依赖的关系,而是弱依赖的关系,也就是说,即使整体不存在了,部分仍然存在。例如,一个部门由多个员工组成,如果部门撤销了,人员不会消失,人员依然存在。
组合关系的特点是,整体由部分构成,但是整体和部分之间是强依赖的关系,如果整体不存在了,部分也随之消失。例如,一个公司由多个部门组成,如果公司不存在了,部门也将不存在。
可以说,组合关系是一种强依赖的、特殊的聚合关系。
在UML图中,聚合关系用一条带空心菱形箭头的直线表示,组合关系用一条带实心菱形箭头的直线表示。聚合与组合在UML图上的区别如图所示。

AQS的核心成员_第4张图片

由于显式锁与AQS之间是一种强依赖的聚合关系,如果显式锁的实例销毁,其聚合的AQS子类实例也会被销毁,因此显式锁与AQS之间是组合关系。

你可能感兴趣的:(JAVA,AQS核心成员,1024程序员节)