JDK锁的基础--AQS实现原理(一)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Q_AN1314/article/details/79868055

AQS全称是AbstractQueuedSynchronizer,是jdk中用来实现锁的基础框架,比如ReentrantLockReadWriteLock以及Condition的实现和AQS密切相关。说到AQS,等来介绍一下CLH锁,CLH锁是用来实现自旋锁的一种方式,其大概原理是用一个队列把等待锁的线程保存起来,自旋等待。但是CLH比较特别的地方在于,在某种程度上,每个节点的行为是由其前驱节点的状态来决定的,如下图所示:
这里写图片描述

其中Node 1代表持有锁的线程,Node 2和Node 3代表等待锁的线程,每个Node 含有一个prev字段和一个status字段,prev字段指向队列中的前一个节点,status字段代表当前节点的状态(是否已经释放了锁)。每个Node初始化时status为true,表示需要获取锁,当获取到锁并且释放了锁之后status变成false,这样其后继节点检测到prev.status == false的时候就表示轮到自己来操作了,也就是自己获得了锁。
CLH队列入队的操作需要一个原子的操作,假设新建了一个节点node:

do {
    prev = tail;
} while(!tail.compareAndSet(prev, node))

出队的时候需要检测其前驱节点的status字段,满足条件后把队列的head节点设置成当前节点即可:

while(prev.status != false);
head = node;

CLH锁的一个好处是入队和出队操作都非常快,并且不需要加锁,而且检测是否有线程正在等待也非常快(只需判断队列的head是否等于tail)。另外CLH队列把状态分散到各个节点,这样就避免了一些内存方面的数据竞争。

在某个节点的前驱节点因为超时或者其它原因被取消的时候,可以用该前驱节点的prev字段来找到一个没有被取消的节点的status来判断当前节点是否持有锁。

CLH锁由于每个线程都是自旋等待,并且不停地轮询另一个线程的状态,所以在NUMA架构的处理器上面可能造成性能损失,在这种情况下可以使用MCS锁,本文就不介绍了。

AQS使用了类似的队列来放置等待的线程,但是和CLH锁不一样的地方有几点,首先 CLH锁队列中的每个线程都是自旋等待,而AQS队列中的线程一般是阻塞等待的。另外AQS队列的Node类包含的信息要比CLH队列的Node类丰富,这也是意料之中的,下面来介绍一下AQS的Node类:

    static final class Node {
        //以下定义了Node类waitStatus字段的几种状态
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;

        //该Node对象的状态,用于控制自身和其后继节点的行为
        volatile int waitStatus;
        //前驱节点
        volatile Node prev;
        //后继节点
        volatile Node next;
        //该Node代表的线程
        volatile Thread thread;
    }

上面的Node类并不完全,只是把实现基本功能的字段列举了出来,没有列出实现共享锁需要的字段。来看一下waitStatus

  1. SIGNAL表示后继节点正处于或者将要处于被阻塞的状态下,当前节点释放锁或者被取消的时候需要唤醒后继节点。AQS的acquire方法中会先把节点的前驱节点的状态设置成SIGNAL状态,然后调用tryAcquire方法尝试获取锁,如果失败的话会阻塞该节点。
  2. CANCELLED表示该节点由于超时或者中断已经被取消。一个处于取消状态的节点永远都只会是取消状态,并且其代表的线程不会再阻塞。
  3. CONDITION表示节点处在Condition对象的等待队列上。这个队列和AQS对象的队列是两个队列,Condition对象的等待队列只有在条件满足的情况下才会转移到AQS的等待队列上。如果没有使用Condition接口,那么这个状态不会出现。暂时无需考虑这种状态。

另外,AQS类本身维护了一个int类型的状态字段state,并且提供了接口用于修改和查询这个状态。state字段反映的是锁的状态,是否被某个线程持有,还是暂时是空闲的。AQS中还有两个字段:

    private transient volatile Node head;
    private transient volatile Node tail;

分别用于表示AQS等待队列的头节点和尾节点。

AQS也从AbstractOwnableSynchronizer类继承了一个字段private transient Thread exclusiveOwnerThread;,指向正持有锁的线程。

展开阅读全文

没有更多推荐了,返回首页