首先我们从java.util.concurrent.locks包中的AbstraceQueuedSynchronizer说起,在下文中称为AQS。

  AQS是一个用于构建锁和同步器的框架。例如在并发包中的ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock等都是基于AOS构建,这些锁都有一个特点,都不是直接扩展自AQS,而是都有一个内部类继承自AQS。为什么会这么设计而不是直接继承呢?简而言之,锁面向的是使用者,同步器面向的是线程控制,在锁的实现中聚合同步器而不是直接继承AQS很好的隔离了二者所关注的领域。

  AbstractQueuedSynchronizer在内部依赖一个双向同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将该线程和等待状态信息构造成一个节点并将其加入到同步队列中。Node节点以AQS的内部类存在,其字段属性如下:

AbstractQueuedSynchronizer$Node

属性

描述

volatile int waitStatus

等待状态,并不是同步状态,而是在队列中的线程节点等待状态(Node节点中一共定义四种状态)

CANCELLED = 1    //线程由于超时或被中断会被取消在队列中的等待,被取消了的线程不会再被阻塞,即状态不会再改变

SIGNAL = -1    //后继节点处于等待状态,当前节点释放锁或者取消等待时会通知后继节点

CONDITION = -2    //暂时忽略,涉及Condition

PROPAGATE = -3    //下一次共享式同步状态获取将会无条件传播下去

volatile Node prev

前驱节点

volatile Node next

后继节点

volatile Thread thread

保持对当前获取同步状态线程的引用

Node nextWaiter

等待队列中的后继节点,同时也表示该节点是共享模式(SHARED)还是独占(EXCLUSIVE)模式。它们共用一个字段。因为在等待队列中的线程一定是独占模式。所以如果nextWatiter == SHARED,那么表示该节点为共享模式。

  在AQS同步器中由一个头节点和尾节点来维护这个同步队列。

AbstractQueuedAbatract

属性

描述

private transient volatile Node head

同步队列头节点

private transient volatile Node tail

同步队列尾节点

  

  以上内容我们需要知道一点的就是:同步器中是依靠一个同步队列来完成的同步状态管理,当线程获取锁(或者称为同步状态)失败时,会将线程构造为一个Node节点新增到同步队列的尾部。

  在锁的获取当中,并不一定是只有一个线程才能持有这个锁(或者称为同步状态),所以此时有了独占模式和共享模式的区别,也就是在Node节点中由nextWait来标识。比如ReentrantLock就是一个独占锁,只能有一个线程获得锁,而WriteAndReadLock的读锁则能由多个线程同时获取,但它的写锁则只能由一个线程持有。本章先介绍独占模式下锁(或者称为同步状态)的获取与释放,在此之前要稍微提一下“模板方法模式”,在AQS同步器中提供了不少的模板方法,关于模板方法模式可以移至《模板方法模式》,总结就是一句话:定义一个操作中的算法的骨架,而将一些步骤的实现延迟到子类中。

  1)独占模式同步状态的获取

AbstractQueuedSynchronizer

方法

描述

public final void acquire(int arg)

独占模式下获取同步状态,忽略中断,即表示无论如何也会在获得同步状态后才返回。

  此方法即为一个模板方法,它的实现代码如下:

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

  tryAcquire:AQS中有一个默认实现,其默认实现即抛出一个UnsupportedOperationException异常,意为默认下独占模式是不支持此操作的。而这个操作在子类又是怎样的呢?我们可以通过查看ReentrantLock中的Sync实现:

//ReentrantLock
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}

  可以看到在AQS的其中一个实现中,ReentrantLock$Sync对它进行了重写,具体意义在这里不做讨论。这个在AQS定义的方法表示该方法保证线程安全的获取同步状态,如果同步状态获取失败(返回false)则构造同步节点并将节点加入到同步队列的尾部,这个操作即是addWaiter方法的实现:

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //将线程构造成Node节点。
/*尝试强行直接挂到同步队列的尾部*/
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
/*如果此时有多个线程都在想把自己挂到同步队列的尾部,上面的操作就会
失败,此时将“无限期”线程安全的等待着挂到同步队列的尾部*/
enq(node);
return node; }

  在enq的实现中实际就是一个for“死循环”,其目的就是直到成功地添加到同步队列尾部才推出循环。

  在获取同步状态失败(tryAcqurie) ->构造节点(addWaiter)->添加到同步队列尾部(addWaiter)过后,接下来就是一个很重要的操作acquireQueued自旋。这个动作很重要,其目的就在于每个节点都各自的在做判断是否能获取到同步状态,每个节点都在自省地观察,当条件满足获取到了同步状态则可以从自旋过程中退出,否则继续。

final boolean acquireQueued(final Node node, int qrg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}

  从上面的代码实现我们可以看到,尽管每个节点都在“无限期”的获取锁,但并不是每个节点能有获取锁的这个资格,而是当它的前驱节点是头节点时才会去获取锁(tryAcquire)。当这个节点获取同步状态时,接下来的方法shouldParkAfterFailedAcquire则会判断当前线程是否需要被阻塞,而这个判断方法则是通过它的前驱节点的waitStatus判断。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //首先获取当前节点的前驱节点等待状态
if (ws == Node.SIGNAL) //当前线程需要被阻塞,即需要被unpark(唤醒)
return true;
if (ws > 0) { //pred.waitStatus == CANCELLED
do {
node.prev = pred = pred.prev; //前驱节点等待状态已经处于取消,即不会再获取同步状态时,把前驱节点从同步状态中移除。
} while (pred.waitStatus > 0);
pred.next = node;
} else { //pred.waitStatus == CONDITION || PROPAGATE
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

  如果调用该方法判断为当前线程需要被阻塞(返回true),则接着执行parkAndCheckInterrupt阻塞当前线程,直到当前线程被唤醒的时候才从parkAndCheckInterrupt返回。

关于独占模式获取同步状态可以总结为下面一段话:

  AQS的模板方法acquire通过调用子类自定义实现的tryAcquire获取同步状态失败后->将线程构造成Node节点(addWaiter)->将Node节点添加到同步队列对尾(addWaiter)->每个节点以自旋的方法获取同步状态(acquirQueued)。在节点自旋获取同步状态时,只有其前驱节点是头节点的时候才会尝试获取同步状态,如果该节点的前驱不是头节点或者该节点的前驱节点是头节点单获取同步状态失败,则判断当前线程需要阻塞,如果需要阻塞则需要被唤醒过后才返回。

  2).独占模式同步状态的释放

AbstractQueuedSynchronizer

方法

描述

public final boolean release(int arg)

释放同步状态,并唤醒后继节点

  当线程获取到了同步状态并且执行了相应的逻辑过后,此时就应该释放同步状态。

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中的release释放同步状态和acquire获取同步状态一样,都是模板方法,tryRelease释放的具体操作都有子类去实现,父类AQS只提供一个算法骨架。

private void unparkSucessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) //ws != CANCELLED
compareAndSetWaitStatus(node, ws, 0); //利用CAS将当前线程的等待状态置为CANCELLE
Node s = node.next;
if (s == null || s.waitSatatus > 0) { //如果当前线程的后继节点为空,则从同步队列的尾节点开始向前寻找当前线程的下一个不为空的节点
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = r;
}
if (s != null)
LockSuport.unpark(s.thread); //如果当前线程的后继节点不为空,则调用LockSuport.unpark唤醒其后继节点,使得后继节点得以重新尝试获取同步状态
}

  对AQS的源码解读才刚刚开始,本节只介绍了AQS在内部使用一个同步队列来管理同步状态,并且介绍了在AQS在模板方法模式的基础上实现独占模式同步状态的获取与释放。下一节会继续解读AQS共享模式下同步状态的获取与释放。

2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放的更多相关文章

  1. 3.从AbstractQueuedSynchronizer(AQS)说起(2)——共享模式的锁获取与释放

    在上节中解析了AbstractQueuedSynchronizer(AQS)中独占模式对同步状态获取和释放的实现过程.本节将会对共享模式的同步状态获取和释放过程做一个解析.上一节提到了独占模式和共享模 ...

  2. Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...

  3. AQS源码深入分析之独占模式-ReentrantLock锁特性详解

    本文基于JDK-8u261源码分析 相信大部分人知道AQS是因为ReentrantLock,ReentrantLock的底层是使用AQS来实现的.还有一部分人知道共享锁(Semaphore/Count ...

  4. 4.从AbstractQueuedSynchronizer(AQS)说起(3)——AQS结语

    前两节的内容<2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放> .<3.从AbstractQueuedSynchronize ...

  5. AbstractQueuedSynchronizer AQS框架源码剖析

    一.引子 Java.util.concurrent包都是Doug Lea写的,来混个眼熟 是的,就是他,提出了JSR166(Java Specification RequestsJava 规范提案), ...

  6. 8.初识Lock与AbstractQueuedSynchronizer(AQS)

    1. concurrent包的结构层次 在针对并发编程中,Doug Lea大师为我们提供了大量实用,高性能的工具类,针对这些代码进行研究会让我们对并发编程的掌握更加透彻也会大大提升我们队并发编程技术的 ...

  7. 初识Lock与AbstractQueuedSynchronizer(AQS)

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  8. 全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(二)资源的获取和释放

    上期的<全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(一)AQS基础>中介绍了什么是AQS,以及AQS的基本结构.有了这些概念做铺垫之后,我们就可以正 ...

  9. 013-并发编程-java.util.concurrent.locks之-AbstractQueuedSynchronizer-用于构建锁和同步容器的框架、独占锁与共享锁的获取与释放

    一.概述 AbstractQueuedSynchronizer (简称AQS),位于java.util.concurrent.locks.AbstractQueuedSynchronizer包下, A ...

随机推荐

  1. CodeFirst的一些操作!!

    CodeFirst的一些操作!! 转载 2016-08-05 21:03:32 1 首先是codefirst怎么做,这个首先肯定要引入EntityFramework,然后在model中创建实体类,例如 ...

  2. js实现哈希表(HashTable)

    在算法中,尤其是有关数组的算法中,哈希表的使用可以很好的解决问题,所以这篇文章会记录一些有关js实现哈希表并给出解决实际问题的例子. 第一部分:相关知识点 属性的枚举: var person = { ...

  3. 性能调优之MYSQL高并发优化下

    三.算法的优化 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写..使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效 ...

  4. ElasticSearch 基本概念

    Elasticsearch是一个接近实时的搜索平台,就是说从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟,通常是一秒钟 集群 一个集群通常有一个或多个elasticsearch节点组成,给这些 ...

  5. WPF自定义控件(1)——仪表盘设计[1]

    0.小叙闲言 又接手一个新的项目了,再来一次上位机开发.网上有很多控件库,做仪表盘(gauge)的也不少,功能也很强大,但是个人觉得库很臃肿,自己就计划动手来写一个控件库,一是为学习,二是为了项目.下 ...

  6. 07 The VC Dimension

    当N大于等于2,k大于等于3时, 易得:mH(N)被Nk-1给bound住. VC维:最小断点值-1/H能shatter的最大k值. 这里的k指的是存在k个输入能被H给shatter,不是任意k个输入 ...

  7. 混合式app ionic2.x 手动搭建开发环境教程分享(nodejs,jdk,ant,androidsdk)

    1.ionic简介 为什么选用ionic: 彻底开源且免费 性能优异 基于红的发紫的AngularJs 漂亮的UI 强大的命令行(基于更热门的nodejs) 开发团队非常活跃 ngCordova,将主 ...

  8. es6基础系列一:let和const

    let 声明变量,可以说是具有作用域的var,用于声明变量,主要规则如下: 1 let声明的变量拥有块级作用域 { let i = 1; console.log(i); // 1 } console. ...

  9. 快速排序算法回顾 --冒泡排序Bubble Sort和快速排序Quick Sort(Python实现)

    冒泡排序的过程是首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则将两个记录交换,然后比较第二个记录和第三个记录的关键字.以此类推,直至第n-1个记录和第n个记录的关键字进行过比较为止 ...

  10. Spring+SpringMVC+MyBatis+easyUI整合优化篇(十一)数据层优化-druid监控及慢sql记录

    本文提要 前文也提到过druid不仅仅是一个连接池技术,因此在将整合druid到项目中后,这一篇文章将去介绍druid的其他特性和功能,作为一个辅助工具帮助提升项目的性能,本文的重点就是两个字:监控. ...