0%

AQS源码阅读笔记

  1. Doug Lea在他的论文中写道:

    • JUC同步器框架的主要性能目标是实现可伸缩性,也即:当发生锁竞争时,可预测地保持效率。在理想的情况下,无论多少线程尝试通过同步点,所需的开销都是常量。(更确切的讲)其中一个主要目标是:某一线程被允许通过同步点但还没有通过的情况下,使其耗费的总时间最少。
    • 实现同步器的上述目标包含了两种不同的使用类型。大部分应用程序是最大化其总的吞吐量,容错性,并且最好保证尽量减少饥饿的情况(非公平锁)。然而,对于那些控制资源分配的程序来说,更重要是去维持多线程读取的公平性,可以接受较差的总吞吐量(公平锁)。没有任何框架可以代表用户去决定应该选择哪一个方式,因此,应该提供不同的公平策略。
  2. AQS队列的head node很特殊,它属于一个占位node,其成员变量pre、next、thread都为null,也就是说队列中第2个node才是真正排队的队首。

  3. 对于AbstractQueuedSyncchronizer的入队函数:enq,当第一次调用(也即初始化队列)时,内部的for循环至少循环2次,第一次设置head、第二次设置tail。

  4. AQS的公平锁实现FairSync中,获取锁的流程为(假定请求锁的线程名为t2):

    • 首先尝试获取锁(FairSync::tryAcquire):
      • 如果发现state == 0(没有人拿到锁),并且有资格拿锁(也即hasQueuedPredecessors返回false,从字面意思讲,这个函数代表是否有线程在当前线程前面排队中,返回true的条件需要同时满足两个:1、队列已经初始化,2、队列中只有一个占位node(前面有一个node在enq一半时会出现这种情况)或者首个排队node的线程不是本线程),则尝试CAS设置state为0,如果设置成功,则获取锁成功并返回。
      • 如果发现state != 0,并且当前线程获取了锁,则增加锁的数量,并且获取锁成功并返回。
    • 如果没有获取到锁:先CAS入队(AbstractQueuedSynchronizer::addWaiter,重点1),其流程为:
      • 检查队列是否已经初始化(tail != null),如果已经初始化则CAS设置当前node为队尾
      • 如果队列已经初始化或者上一步CAS设置队尾失败,则自旋进行入队操作(AbstractQueuedSynchronizer::enq)。
    • 入队后,开始自旋(AbstractQueuedSynchronizer::acquireQueued),其流程为:
      • 先再一次检测能否获取锁(设计思路是:t2入队后,可能前面的线程正好释放了锁,此时t2虽然在队中,但是可能就是队首(也即是队列的第2个node),那就不需要排队)
      • 如果还是拿不到;检测是否需要park,检测条件为:pre.waitState == 0(并且前前一个node的waitState是由当前节点修改为0的,为什么呢?因为前一个node已经睡眠中,无法修改自己,有意思!),如果需要(第一次自旋时必然不成立,乐观锁的天性:尽量不park),则调用LockSupport.park阻塞当前线程。
      • 如果拿到锁直接返回
  5. 一个线程尝试获取ReentrantLock公平锁时,最多会尝试3次获取锁,发生的场景是锁已经被其他线程占有,但是该请求线程是队列的队首,该请求首先会尝试获取,获取失败后,在入队park前会自旋2次尝试获取。最少会1次尝试锁,发生的场景是锁已经被其他线程占有,且该请求线程入队后也不是队首。