AQS源码解析

1 篇文章 0 订阅

这里主要记录一下ReentrantLock的工作流程,以稍微复杂一些的非公平锁展开

主要类架构图

image-20220911222854124

AbstractQueuedSynchronizer类底层的数据结构是使用CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。

image

主要字段介绍,之后会多次提到

Node中的重要字段

  • waitStatus,节点的等待状态,下面为状态枚举

  • CANCELLED,值为1,表示当前的线程被取消。

  • SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作。

  • CONDITION,值为-2,表示当前节点在等待condition,也就是在condition queue中。

  • PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行。

  • 值为0,新建一个Node的默认值,表示当前节点在sync queue中,等待着获取锁。

AbstractQueuedSynchronizer中的重要字段

  • head指针指向CLH队列头结点(CLH队列拥有一个dummyNode)
  • tail指针指向CLH队列的尾节点
  • state:AQS同步状态,在独占模式下,1代表已经有线程占用,0代表无线程占用
ReentrantLock加锁

场景:线程A、B、C竞争同一把锁,线程A先获得锁,并执行业务代码,持续一个很长的时间,这时B、C按顺序来竞争这把锁。

  1. 线程A获取锁

    image-20220911225655925

    此时AQS的state还为默认值0,CAS将state的值更改为1,设置独占线程为线程A。

  2. 线程B来获取锁

    image-20220911225655925

    当前state的值已经为1了,所以走acquire(1)

    image-20220911231247256

    这部分是核心代码块

    1. 首先是tryAcquire()方法,这时AQS中模版模式的运用,具体由该抽象类的子类实现。

      image-20220911230510734

      image-20220911230716807

      state为1,当前的独占线程是A而不是B,所有返回false。

    2. 此时走到addWatier方法,参数为一个独占模式的Node节点

      image-20220911231457039

      tail指针当前还是null,所以进入到enq方法

      image-20220911231621146

      这里是一个死循环

      第一次循环,t为null,CAS更新head指针指向一个dummyNode(虚拟节点),tail指针也指向这个地址。

      image-20220911232027871

      第二次循环,t指向dummyNode,非空,将Node(B)设置为该dummyNode的后继节点,退出循环。

      image-20220911232418709

    3. 走到acquireQueued方法,参数为上述加入到CLH的独占模式节点Node(B)和1。

      image-20220911232727061

      进入到死循环

      第一次循环,p指向Node(B)的前驱节点,即dummyNode,head指向dummyNode,进入到tryAcquire方法。

      image-20220911230716807

      同上,因为state为1且当前独占线程为A,所以返回false。

      进入到shouldParkAfterFailedAcquire方法,参数为dummyNode和当前节点Node(B)

      后面会发现,都是后继节点将前驱节点的waitStatus更新为-1

      image-20220911233255664

      ws为0,CAS将dummyNode的watiStatus更新为-1,返回true

      进入parkAndCheckInterrupt

      image-20220911233710370

      线程B挂起,至此,线程B并没有完全结束,等到线程B被unpark后,将继续执行。

      image-20220912102134654

  3. 线程C来获取锁,此时线程A仍未释放锁,线程B已经进入到CLH队列中。这里和B线程获取锁大致雷同,故仅介绍一些不动的代码。

    这里说一下tryAcquire方法,若占用锁的线程释放了锁,此时CLH队列中已经有线程被挂起等待了,突然来了一个线程竞争锁,公平锁和非公平锁有不同的表现

    非公平锁:突然来的线程可以直接插队。

    image-20220912101339039

    公平锁:如果CLH队列中有线程被挂起等待,则需要排队。

    image-20220912101508497

    线程C进入CLH队列,并将Node(B)的waitStatus更新为-1,线程C挂起等待。

    image-20220912102246568

ReentrantLock解锁
正常场景

继续使用上述A、B、C线程的场景,此时A线程占有锁在执行业务代码,B、C线程在CLH队列中挂起等待

现在线程A执行完毕,释放锁。

image-20220912102246568

image-20220912102724968

进入release方法,参数为1

image-20220912102800788

将独占线程置空,将state更新为0,返回true

image-20220912102724968

进入该方法体,h指向dummyNode,进入unparkSuccessor方法,释放dummyNode的后继节点,即Node(B)

image-20220912103210438

将dummyNode的waitStatus更新为0,s指向Node(B),执行LockSupport.unpark方法将B线程释放。

image-20220912104225734

书接上回,B线程挂起处。

image-20220911233710370

返回线程B的中断状态并重置线程B的中断状态

image-20220911232727061

再次走这个循环体,p指向dummyNode,进入到tryAcquire方法,参数为1

image-20220912104618808

当前线程为B,state为0,CAS更为state为1,设置当前独占线程为线程B,返回true

进入if代码块,head指针指向Node(B),dummyNode释放,faild置为false,返回线程B中断标记,若为true,则调用interrupt方法将线程中断状态置为true。

至此线程B独占锁,执行业务代码。

image-20220912105849467

之后,线程B释放锁,线程C获取锁,同上,不再赘述。

异常场景

image-20220912110054339

线程在CLH队列中,发生异常,未能走到866行将failed状态置为false,进入到finally代码块执行cancelAcquire方法.

场景:

image-20220912220423265

线程D执行发生异常,进入到cancelAcquire方法,参数为Node(D),Node©已经是取消状态

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

代码执行,Node(D)将持有的线程引用置为null,跳过Node©,Node(D)的prev指针指向Node(B),Node(D)的waitStatus置为1,CAS更新Node(null)(之前的Node(B))的next指针指向Node(E),Node(D)的next指针指向自己,帮助GC,至此节点C、D出队。

image-20220912220600711

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值