在了解AQS之前我们需要来认识一下这个工具类,因为该工具类是创建锁和其他同步类的基础

LockSupport

该工具类的主要作用是挂起和唤醒线程

LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的,LockSupport是使用Unsafe类实现的,下面介绍几个重要的函数

void park()

public static void park() {UNSAFE.park(false, 0L);
}

如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,如果没有拿到LockSupport关联的许可证,则调用线程会被禁止参与线程的调度,会被阻塞挂起

在其他线程调用unpark(Thread thread)方法并且将当前线程作为参数时,调用part()方法而被阻塞的方法会返回,另外,如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者被虚假唤醒,则阻塞线程也会返回。

void unpark(Thread thread)

public static void unpark(Thread thread) {if (thread != null)UNSAFE.unpark(thread);
}

当一个线程调用unpark()时,如果参数thread线程没有持有thread与LockSupport关联的许可证,则让thread持有,如果thread之前因调用park而被挂起,则调用unpark后,该线程会被唤醒,如果thread之前没有调用park,则unpark方法后,再使用park会立即返回

void parkNanos(long nanos)

与park方法类似,但该方法的不同之处在于,如果没有拿到许可证,则调用线程会被挂起nanos时间后自动返回

public static void parkNanos(long nanos) {if (nanos > 0)UNSAFE.park(false, nanos);
}

void park(Object blocker)

public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);UNSAFE.park(false, 0L);setBlocker(t, null);
}

Thread类里面有个变量volatile Object parkBlocker,用来存放park方法传递的blocker对象,也就是把blocker变量存放到了调用park方法的成员变量里,使用带有blocker参数的park方法,线程堆栈可以提供更多有关阻塞对象的信息

抽象同步队列AQS

AbstractQueuedSynchronizer它是实现同步器的基础组件,并发包中锁的底层就是AQS实现的

AQS属性

由图可知,AQS是一个FIFO的双向队列,其内部通过head和tail记录队首和队尾元素,队列元素的类型为Node。在AQS中维持了一个单一的状态信息state可以通过getState,setState等修改其值,对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数;对于semaphore来说,state用来表示当前可用信号的个数,对于CountDownlatch来说,state用来表示计数器当前的值

其中Node中的thread变量用来存放进入AQS队列里面的线程Node结点内部的SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的,EXCLUSIVE用来标记线程是获取独占资源时被挂起放入AQS队列的,waitStatus记录当前线程等待状态,可以为CANCELLED(线程取消)、SIGNAL(线程唤醒)、CONDITION(线程在条件队列里等待)、PROPAGATE(释放共享资源时需要通知其他结点),prev记录当前结点的前驱结点,next记录当前结点的后继结点

AQS有个内部类ConditionObject,用来结合锁实现线程同步。ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列,ConditionObject是条件变量,每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量await方法后被阻塞的线程

AQS线程同步

对于AQS来说,线程同步的关键是对状态值state进行操作根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。在独占方式下获取和释放资源使用的方法为:void acquire(int arg) boolean release(int arg)

在共享方式下获取和释放资源的方法为void acquireShared(int arg) void releaseShared(int arg)

在独占式后去资源是与线程绑定的,就是说如果一个线程获取到了资源,就会标记是这个线程获取到了,其他线程操作state会发现当前资源不是自己持有,就会失败阻塞

对应共享方式的资源与线程是不相关的,当多个线程去请求资源时通过CAS方式竞争获取资源,当一个线程获取到了资源以后,另外一个线程再次去获取如果当前资源还能满足它的需要,则当前线程只需要使用CAS方式进行获锁即可

在独占方式下,获取和释放资源的流程如下:

  1. 当一个线程调用acquire(int arg)方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型Node.EXCLUSIVE的结点插入到AQS阻塞队列的尾部,并调LockSupport.park(this)挂起自己

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
    }
    
  2. 当一个线程调用release(int arg)方法时会尝试使用tryRelease操作释放资源,这里设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面的一个线程,被激活的线程尝试tryAcquire尝试,看当前状态变量的值是否能够满足自己的需要,满足该线程被激活,然后继续向下运行,否则还是会被AQS队列挂起

    public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
    }
    

需要注意的是,AQS类并没有提供可用的tryAcquire和tryRelease方法,这两个方法需要具体的子类去实现,这里体现了模板方法的设计模式思想

在共享方式下,获取和释放资源的流程如下:

  1. 当一个线程调用acquireShared(int arg)方法获取共享资源时,会首先使用tryAcquireShared方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型Node.SHARED的结点插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)挂起自己

    public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)doAcquireShared(arg);
    }
    
  2. 当一个线程调用releaseShared(int arg)方法时会尝试使用tryReleaseShared操作释放资源,这里设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面的一个线程,被激活的线程尝试tryReleaseShared尝试,看当前状态变量的值是否能够满足自己的需要,满足该线程被激活,然后继续向下运行,否则还是会被AQS队列挂起

    public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;
    }
    

需要注意的是,AQS类并没有提供可用的tryAcquireShared和tryReleaseShared方法,这两个方法需要具体的子类去实现

AQS入队操作

当一个线程获取锁失败后该线程会被转换为Node结点,然后会调用下面方法插入到AQS队列中

private Node enq(final Node node) {for (;;) {//1.指向尾结点Node t = tail;//2.第一次循环:如果t为空cas操作给头结点赋值,然后头结点赋值尾结点,当队列为空时if (t == null) {if (compareAndSetHead(new Node()))tail = head;} else {//3.第二次循环:将待插入结点的前结点赋值为tnode.prev = t;//4.cas操作给尾结点赋值为nodeif (compareAndSetTail(t, node)) {//5.cas操作成功则将t结点的next结点赋值给nodet.next = node;return t;}}}
}

AQS条件变量的支持

lock.newCondition()的作用起始时new了一个在AQS内部声明的ConditionObject对象,ConditionObject时AQS的内部类,可以访问AQS内部的变量和方法,每个条件变量内部都维护了一个条件队列,用来存放调用条件变量的await()方法时阻塞的线程。,注意时条件队列和AQS的队列不是一回事

当线程调用条件变量的await()方法时,(调用await()方法的前提是获得锁)在内部会创建一个类型为Node.CONDITION的node结点,然后将该结点插入到条件队列末尾,之后当前线程会释放获取的锁也就是操作锁对应的state变量,并被阻塞挂起

一句话总结,await会释放锁并把当前线程阻塞挂起,在该条件队列中会插入一个node结点,但释放锁的前提是占有锁

public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();//创建node结点并插入到条件队列末尾Node node = addConditionWaiter();//释放当前线程获取的锁int savedState = fullyRelease(node);int interruptMode = 0;while (!isOnSyncQueue(node)) {//挂起当前线程LockSupport.park(this);if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);
}

当线程A因为await方法阻塞时,线程B调用条件变量的signal方法时,在内部会把条件队列里面队头的一个线程节点从条件队列里面移除并放入AQS的阻塞队列里面,然后激活这个线程

public final void signal() {if (!isHeldExclusively())throw new IllegalMonitorStateException();Node first = firstWaiter;if (first != null)doSignal(first);
}

波吉学源码——AQS源码剖析相关推荐

  1. 多线程:AQS源码分析

    AQS 源码分析 概述 Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比s ...

  2. AQS源码阅读笔记(一)

    AQS源码阅读笔记 先看下这个类张非常重要的一个静态内部类Node.如下: static final class Node {//表示当前节点以共享模式等待锁static final Node SHA ...

  3. 刨根问底-AQS源码解析

    刨根问底-AQS源码解析 CLH同步队列 入队列 出队列 同步状态的获取与释放 独占式同步状态获取 独占式获取响应中断 独占式超时获取 独占式同步状态释放 共享式同步状态获取 共享式同步状态释放 阻塞 ...

  4. Java并发之AQS源码分析ReentranLock、ReentrantReadWriteLock、Condition

    基于AQS的独享锁和共享锁的源码分析 基本概念说明 锁的基本原理思考 测试环境 实现方案1 实现方案2 独占锁:ReentrantLock源码分析 类依赖和类成员变量说明 加锁过程,入口方法:lock ...

  5. JAVA毕业设计黑格伯爵国际英语贵族学校官网计算机源码+lw文档+系统+调试部署+数据库

    JAVA毕业设计黑格伯爵国际英语贵族学校官网计算机源码+lw文档+系统+调试部署+数据库 JAVA毕业设计黑格伯爵国际英语贵族学校官网计算机源码+lw文档+系统+调试部署+数据库 本源码技术栈: 项目 ...

  6. Java深海拾遗系列(10)--- Java并发之AQS源码分析

    AQS 全称是 AbstractQueuedSynchronizer,顾名思义,是一个用来构建锁和同步器的框架,它底层用了 CAS 技术来保证操作的原子性,同时利用 FIFO 队列实现线程间的锁竞争, ...

  7. AQS源码二探-JUC系列

    Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/cou ...

  8. 并发-AQS源码分析

    微信搜索:"二十同学" 公众号,欢迎关注一条不一样的成长之路 一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQu ...

  9. 斐波那契数列(递归+源码+注释)

    斐波那契数列(递归+源码+注释) 公元 1202 年,意大利数学家莱昂纳多·斐波那契提出了具备以下特征的数列: 前两个数的值分别为 0 .1 或者 1.1: 从第 3 个数字开始,它的值是前两个数字的 ...

最新文章

  1. python3运行报错:TypeError: Object of type 'type' is not JSON serializable解决方法
  2. 张亚勤清华AIR战队首次亮相,这阵容不是一般强
  3. read web.config
  4. Xamarin.Forms: 无限滚动的ListView(懒加载方式)
  5. 删除Word文档中的全部汉字
  6. JavaScript文档对象模型DOM节点操作之创建和添加节点(5)
  7. vs 编译nmake工程
  8. 基于三维WebGL技术的公安三维项目
  9. java需要知道哪些英语单词_70个学习JAVA必背的英语单词,了解下
  10. 微信支付的appid,appsecret,商户号mchid,微信交易支付密钥在哪里查看
  11. matlab保存bln文件,气象万千(冯锦明课题组)-软件程序
  12. long tail(长尾理论)
  13. Python中文社区开源项目扶持计划
  14. chrome浏览器收藏夹恢复
  15. 待办工作是什么意思?
  16. 矢量绘图软件:Sketch 56 for mac
  17. paddle 学习总结与使用指南 笔记(一)
  18. 118云原生编程语言Golang学习笔记
  19. 三星Galaxy S8打开USB调试
  20. Laravel页面多个列表 多个分页

热门文章

  1. Linux命令行执行sqlite3命令创建表格,插入数据,获取数据
  2. 程序员不要以为技术牛逼就行了,这些你得知道的职场潜规则,助你一路高升
  3. 单元测试及框架简介 --junit、jmock、mockito、powermock的简单使用
  4. 好用的提升视唱练耳能力的软件推荐
  5. 在线图片压缩免费的网站推荐
  6. EXCEL 如何一次全选所有行的两种方法?快捷键 ctrl+shift+↓ 和ctrl+G 定位常量
  7. 互联网公司校招Java面试题总结及答案——微店、去哪儿、蘑菇街
  8. java lamda表达式
  9. 你的计算机无法识别扫描仪,计算机无法检测到照相机或扫描仪,怎么处理呢?
  10. linux df-h命令详细,df命令 – 显示磁盘空间使用情况