• 反射获取AQS中同步队列与等待队列的长度


    • 在使用JUC中的锁时(如ReentrantLockReentrantReadWriteLock),底层维护了两个队列(同步队列和等待队列),但是并没有提供相关的API来获取其队列的长度。我这里通过反射写了一个工具类,可以传入一个Lock实例获取两个队列的长度

    • 在写这个工具类前,需要先完全弄清楚AQS与Lock的结构(包括其中的内部类、内部类子类)。尤其是不能忽略实际上Lock在运行中中创建的Sync对象是其子类NonfairSyncfairSync。如果需要反射获取到AQS中的Node节点,就需要连续两次syncClazz.getSuperclass().getSuperclass()获取父类

    • 下面标题1给出最终工具类,标题2开始分析源码和改造思路,按照这个思路还可以自由地更改AQS锁队列的顺序,指定剔除等

    1.工具类

    1.1代码

    获取同步队列长度:传入Lock实例
    获取等待队列长度:传入Condition实例

    public class AQSUtils {
      /**
       * 获取同步队列长度(线程lock被阻塞 + 正持有锁的 节同步队列点总数) 支持JUC下locks包中所有锁
       *
       * @Author: zjh
       * @Date: 2022/8/23 10:14
       */
      public static int getSyncQueueLength(Lock lock) throws NoSuchFieldException, IllegalAccessException {
        Class lockClazz = lock.getClass();
        //先获取内部类Sync实例,因为Sync继承于AbstractQueuedSynchronizer,从而可以获得Node head节点
        //这里Lock接口下的几个实现类(如ReentrantLock、ReentantReadWriteLock)中Sync都是内部类
        Field syncField = lockClazz.getDeclaredField("sync");
        syncField.setAccessible(true);
        AbstractQueuedSynchronizer                  sync      = (AbstractQueuedSynchronizer) syncField.get(lock);
        Class syncClazz = sync.getClass();
        //***十分需要注意,这里用了两次getSuperclass()
        Class AQSClazz = (Class) syncClazz.getSuperclass().getSuperclass();
        //获取同步队列的头节点  末尾节点
        Field headNode = AQSClazz.getDeclaredField("head");
        headNode.setAccessible(true);
        //这里实际上是同步队列的Node节点,碍于访问修饰符问题,只能通过反射获取next节点的值
        Object head = headNode.get(sync);
        //计算长度
        int    length = 0;
        Object temp   = head;
        while (temp != null) {
          length++;
          Class nodecLazz = temp.getClass();
          Field    nextNode      = nodecLazz.getDeclaredField("next");
          nextNode.setAccessible(true);
          temp = nextNode.get(temp);
        }
        return length;
      }
    
      /**
       * 每个Condition对象中都持有一个等待队列,获取其等待队列长度
       *
       * @Author: zjh
       * @Date: 2022/8/23 10:18
       */
      public static int getWaiteQueueLengthInCondition(Condition condition)
          throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        Class conClazz = condition.getClass();
        Field                      firstWaiterNode = conClazz.getDeclaredField("firstWaiter");
        firstWaiterNode.setAccessible(true);
        Object firstWaiter = firstWaiterNode.get(condition);
        int length = 0;
        Object temp = firstWaiter;
        while(temp!=null){
          length++;
          Class nodeClazz = temp.getClass();
          Field    next      = nodeClazz.getDeclaredField("nextWaiter");
          next.setAccessible(true);
          temp = next.get(temp);
        }
        return length;
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    1.2演示案例

    1. 在main中创建Lock和Condition实例:

       ReentrantLock lock = new ReentrantLock();
       Condition     condition = lock.newCondition();
      
      • 1
      • 2
    2. 创建3个这样的线程:

         //lock加锁后await阻塞————》同步队列不变   等待队列+1
           new Thread(()->{
             lock.lock();
             try {
               condition.await();
             } catch (InterruptedException e) {
               e.printStackTrace();
             }
             lock.unlock();
           }).start();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    3. 创建5个这样的线程:

       //lock加锁 同步队列+1  等待队列不变
       new Thread(()->{
         lock.lock();
       }).start();
      
      • 1
      • 2
      • 3
      • 4
    4. 调用AQSUtils

       TimeUnit.SECONDS.sleep(1);//保证线程全部开启
       int syncQueueLength = AQSUtils.getSyncQueueLength(lock);
       System.out.println(syncQueueLength);
      
       int waiteQueueLengthInCondition = AQSUtils.getWaiteQueueLengthInCondition(condition);
       System.out.println(waiteQueueLengthInCondition);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    5. 结果:同步队列5个 等待队列3个

    6. 同理,调用signal()也会让等待队列-1 同步队列+1,signalAll()会将对应condition中所有等待队列节点全部移入同步队列

    2.源码解析

    这个其实主要是看AQS源码的结构,已知:

    • Lock下的实例的锁相关操作都是调的AQS类中的方法,
    • AQS中有一个内部类Node用于维护队列,AQS另一个内部类ConditionObject也调用了Node,
    • 因此有两个不同维度的Node,一个是同步队列,一个是等待队列

    2.1生成同步队列(用于debug)

    在main线程中运行如下代码,debug可知产生了同步队列的节点

    ReentrantLock lock   = new ReentrantLock(false);
    //用于通知生产者进行生产
    Condition pro = lock.newCondition();
    new Thread(()->{
      //产生同步队列
      lock.lock();
    }).start();
    
    new Thread(()->{
      //产生同步队列
      lock.lock();
    }).start();
    
    new Thread(()->{
      //产生同步队列
      lock.lock();
    }).start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Sync实例的直接Node属性是同步队列,Sync.

    2.2AQS结构

    2.3改造思路

    2.3.1同步队列

    Lock——》Sync——》获取父类的父类AQS——》获取head节点——》循环遍历next节点

    2.3.2等待队列

    Lock——》生成的Condition——》获取firstWaiter节点——》循环遍历nextWaiter节点

  • 相关阅读:
    我用Axure制作了一款火影小游戏 | PM老猫
    java计算机毕业设计vue健身食谱系统源码+mysql数据库+系统+lw文档+部署
    C# - var 关键字
    王干娘和西门庆-UMLChina建模知识竞赛第4赛季第18轮
    godot引擎学习4
    sklearn基础篇(五)-- 线性模型
    ZCC5429 异步升压芯片
    .NET性能优化-推荐使用Collections.Pooled
    【2023米哈游-2】数组相关
    Spring Boot 国际化踩坑指南
  • 原文地址:https://blog.csdn.net/m0_56079407/article/details/126475530