AQS(acquireQueued(Node, int) 3)--队列同步器

1.acquireQueued(Node, int)

   源码:

        final boolean acquireQueued(final Node node, int arg) {

               // 标识是否获取资源失败                

               boolean failed = true;

               try {

                    // 标识当前线程是否被中断过

                   boolean interrupted = false;

                   // 自旋操作

                   for (;;) {

                        // 获取当前节点的前继节点

                       final Node p = node.predecessor();

                       // 如果前继节点为头结点,说明排队马上排到自己了,可以尝试获取资源,若获取资源成功,则执行下述操作

                      if (p == head && tryAcquire(arg)) {

                         // 将当前节点设置为头结点

                        setHead(node);

                         // 说明前继节点已经释放掉资源了,将其next置空,以方便虚拟机回收掉该前继节点

                         p.next = null; // help GC

                         // 标识获取资源成功

                         failed = false;

                         // 返回中断标记

                         return interrupted;

                      }

                      // 若前继节点不是头结点,或者获取资源失败,

                      // 则需要通过shouldParkAfterFailedAcquire函数

                      // 判断是否需要阻塞该节点持有的线程

                     // 若shouldParkAfterFailedAcquire函数返回true,

                     // 则继续执行parkAndCheckInterrupt()函数,

                     // 将该线程阻塞并检查是否可以被中断,若返回true,则将interrupted标志置于true

                     if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())

                           interrupted = true;

                 }

            } finally {

                 // 最终获取资源失败,则当前节点放弃获取资源

                 if (failed)

                        cancelAcquire(node);

            }

      }

    具体看一下shouldParkAfterFailedAcquire函数:

    // shouldParkAfterFailedAcquire是通过前继节点的waitStatus值来判断是否阻塞当前节点的线程的

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

        // 获取前继节点的waitStatus值ws

       int ws = pred.waitStatus;

       // 如果ws的值为Node.SIGNAL(-1),则直接返回true

      // 说明前继节点完成资源的释放或者中断后,会通知当前节点的,回家等通知就好了,不用自旋频繁地来打听消息

      if (ws == Node.SIGNAL) return true;

      // 如果前继节点的ws值大于0,即为1,说明前继节点处于放弃状态(Cancelled)

      // 那就继续往前遍历,直到当前节点的前继节点的ws值为0或负数

      // 此处代码很关键,节点往前移动就是通过这里来实现的,直到节点的前继节点满足

      // if (p == head && tryAcquire(arg))条件,acquireQueued方法才能够跳出自旋过程

      if (ws > 0) {

           do {

                 node.prev = pred = pred.prev;

           } while (pred.waitStatus > 0);

                pred.next = node;

           } else {

              // 将前继节点的ws值设置为Node.SIGNAL,以保证下次自旋时,shouldParkAfterFailedAcquire直接返回true

              compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

         }

          return false;

    }

    parkAndCheckInterrupt()函数则简单很多,主要调用LockSupport类的park()方法阻塞当前线程,并返回线程是否被中断过。

    private final boolean parkAndCheckInterrupt() {

             LockSupport.park(this);

             return Thread.interrupted();

     }

     至此,独占模式下,线程获取资源acquire的代码就跟完了,总结一下过程:

     第一步:首先线程通过tryAcquire(arg)尝试获取共享资源,若获取成功则直接返回,若不成功,

                    则将该线程以独占模式添加到等待队列尾部,

                    tryAcquire(arg)由继承AQS的自定义同步器来具体实现;

      第二步:当前线程加入等待队列后,会通过acquireQueued方法基于CAS自旋不断尝试获取资源,直至获取到资源;

      第三步:若在自旋过程中,线程被中断过,acquireQueued方法会标记此次中断,并返回true。

      第四步:若acquireQueued方法获取到资源后,返回true,则执行线程自我中断操作selfInterrupt()。

      static void selfInterrupt() {

                         Thread.currentThread().interrupt();

      }

 

2.释放资源(独占模式)

    讲完获取资源,对应的讲一下AQS的释放资源过程,其入口函数为:

     public final boolean release(int arg) {

           if (tryRelease(arg)) {

             // 获取到等待队列的头结点h

            Node h = head;

            // 若头结点不为空且其ws值非0,则唤醒h的后继节点

           if (h != null && h.waitStatus != 0)

               unparkSuccessor(h);

               return true;

             }

            return false;

         }

     总结:逻辑并不复杂,通过tryRelease(arg)来释放资源,和tryAcquire类似,tryRelease也是有继承AQS的自定义同步器来具体实现

     函数:

        tryRelease(int) --该方法尝试释放指定量的资源。

        protected boolean tryRelease(int arg) {

                throw new UnsupportedOperationException();

         }

 

学习来源:https://www.jianshu.com/p/0f876ead2846

posted @ 2020-09-01 15:35  小窝蜗  阅读(1663)  评论(2编辑  收藏  举报