ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

AQS源码解读

2021-07-26 22:29:56  阅读:207  来源: 互联网

标签:node Node AQS 队列 解读 源码 线程 节点


  抽象队列同步器(Abstract Queued Synchronizer,AQS)作为并发包JUL中一个基础组件,用来实现各种锁和同步组件,AQS主要由状态state变量、加锁线程和等待队列组成。AQS定义了多线程访问共享资源的框架,AQS定义了Exclusive(Reentrantlock)和share(Semaphore和CountDownLacth)两种资源共享方式,在不同的组件中都有基于AQS的自定义同步器,不同的自定义同步器主要区别在获取state的方式不同。

  AQS内通过一个FIFO队列来管理资源获取线程的排队工作,并通过一个int型的变量来表示线程持有锁的状态,通过CAS锁对int变量进行修改。int变量实现线程之间互斥,state等于1表示共享资源已经被线程加锁;state等于0,表示共享资源没有被人加锁;state大于1表示共享资源已经被同一个线程多次加锁。下图为AQS和其它类之间的一个集成依赖关系。

在这里插入图片描述
在这里插入图片描述

任务队列
java并发包JUC不同的锁内部都对AQS进行了具体实现,下面以ReentrantLock为例,具体说明ReentrantLokc如何通过AQS实现加锁和释放锁。

1.Reentranlock整体加锁过程

  第一个过程是ReentranLock整体的加锁过程,这个过程主要描述了一个新任务的执行时机和新任务入队时机。

在这里插入图片描述

ReentrantLock的实现过程,在ReentrantLock通过集成AQS事项了公平了非公平的同步器,无参方法创建ReentrantLock时创建,内部调用的是非公平同步器。
在这里插入图片描述

public void lock() {
//调用非公平阻塞的lock方法
        sync.lock();
}

final void lock() {
//state初试值为0,表示第一个线程
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);//	不是第一个线程
}

2.任务入队过程

第二个过程是任务阻塞入队的过程,在真正阻塞之前,进入到任务等候队列,会两次尝试去获得资源锁,两次都获取不到,任务线程才会进入阻塞状态,等待前面的线程执行完毕。线程使用了LockSupport的自旋锁进行线程阻塞。

在这里插入图片描述
无法获得共享资源锁的线程会被封装在一个Node中,被阻塞后放在任务队列中等待被唤醒。Node中有一个表示线程状态的标志位waitStatus和记录阻塞线程的Thread变量。
Node中waitStatus的值为:
0:创建一个node的默认值。
CANCELLED(1):表示线程获取锁的请求已经被取消。
CONDITION(-2):表示节点在等待队列中,节点线程等待被唤醒。
PROPAGATE(-3):当先线程处于SHARED情况下,该字段才会被使用。
SIGNAL(-1):表示线程已经准备好了,等待资源被释放了。
最终任务队列中节点状态为:
1) 除了第一个节点,所有节点都处于阻塞状态;
2) 除了尾节点,所有节点waitStatus==Node.SIGNAL。

3.Reentranlock释放锁过程

第三个过程是等待队列中的线程被唤醒的过程,主要描述了等待线程唤醒时机,唤醒线程从等待队列到执行所做的一些动作。
在这里插入图片描述

Q1:为什么在释放锁的过程中从队列尾往前找第一个WaitStatus不为CANCELLED的NODE节点进行释放。

 Node s = node.next;//node为head节点
 if (s == null || s.waitStatus > 0) {
      s = null;
      //从尾部开始查找没有被取消的节点。
      for (Node t = tail; t != null && t != node; t = t.prev)
          if (t.waitStatus <= 0)
              s = t;
  }
  if (s != null)
      LockSupport.unpark(s.thread);

因为Node节点在入队的时候,保证了prev的强一致性,node的next的是弱一致性。维护一致性通常需要牺牲部分性能,为了进一步的提升性能,使用了弱一致性。

node.prev = pred;//无条件执行
if (compareAndSetTail(pred, node)) {
    pred.next = node;//if满足才能执行
    return node;
}

Q2:AQS为什么使用双向队列作为任务等待队列?

AQS使用单向队列也能实现任务队列,为什么还要使用到双向队列,通过head指针和tail指针就可以方便节点的移除和增加,但是在很多场合,对当前节点的操作,需要知道前一个节点的状态,为了节省访问上一个节点的时间复杂度,使用了双向队列,查询当前节点上一个节点的时间复杂度为O(1)。

例如:
 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
           final Node p = node.predecessor();//需要获取前驱节点信息
           ...
 }

Reference

[1] https://www.bilibili.com/video/BV1Hy4y1B78T
[2]https://monkeysayhi.github.io/2017/12/05/%E6%BA%90%E7%A0%81%7C%E5%B9%B6%E5%8F%91%E4%B8%80%E6%9E%9D%E8%8A%B1%E4%B9%8BReentrantLock%E4%B8%8EAQS%EF%BC%881%EF%BC%89%EF%BC%9Alock%E3%80%81unlock/.

标签:node,Node,AQS,队列,解读,源码,线程,节点
来源: https://blog.csdn.net/huhu123444/article/details/119108559

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有