基本概念

AQS(AbstractQueuedSynchronizer),顾名思义,是一个抽象的队列同步器。

  1. 它的队列是先进先出(FIFO)的等待队列
  2. 基于这个队列,AQS提供了一个实现阻塞锁的机制
  3. 最终,基于这个阻塞锁,可以实现多线程的同步

先进先出的等待队列

这个等待队列,是基于CLH锁实现的。
CLH锁是以发明人命名的自旋锁,这个锁是一个基于队列的自旋锁,是对SpinLock,TicketLock的进化。
具体可以参考另外一篇文章,最好是可以自己实现下。

CLH锁的基本原理:

  1. 使用FIFO队列保证公平性
  2. 有当前节点和前置节点,当前节点不断自旋,查询(监听)前置节点的状态(isLocked)(保证了FIFO)
  3. 一系列的前置节点和当前节点构成队列
  4. 当前节点运行完成后,更改自己的状态,那监听当前节点状态的线程就会结束自旋

CLH锁,使用了一个属性tail(尾节点)来关联前置节点和当前节点
lock时:

    1. 新建节点并与当前线程绑定(使用ThreadLocal)
    2. 获取原有的尾节点作为前置节点,设置尾节点为当前节点(这样其他节点就可以挂在当前节点后)
//说明有前置节点
if(原尾节点不为空){
//自旋以等待前置节点状态改变
}

unlock时:

  1. 把当前节点的isLock状态置为false.
  2. (下一个节点的)监听当前节点状态的自旋被解除

总结:
当前节点不断自旋访问前置节点的状态,状态改变时结束自旋

AQS对CLH的增强和进化

AQS基于CLH锁的访问前置节点信息的原理实现,并添加了一些功能:

  1. 支持阻塞而不是一直自旋,竞争激烈时,阻塞性能更好
  2. 支持可重入
  3. 支持取消节点
  4. 支持中断
  5. 支持独占(互斥)和共享两种模式
  6. 支持Condition Condition替代对象监听器(Monitor)用来等待,唤醒线程,用于线程间的协作

AQS基于对CLH的改进提供了实现同步器的机制,解决了获取锁失败后的问题,
子类仅仅需要实现获取/释放锁的具体逻辑。该机制包括:

    1. AQS不关注怎么获取锁/释放锁(如何获取和释放锁,由具体子类实现)。
//获取锁的逻辑,成功返回true
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//释放锁,完全释放(考虑重入)返回true
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//共享模式获取锁
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
//共享模式释放锁
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}

AQS关注的是获取失败后:
2.1 构建CLH队列
2.2 不断自旋
2.2.1 检查前置节点是否是头节点(头节点是持有锁的节点),如果是头节点,则尝试获取锁,获取成功,线程持有锁,否则走下面
2.2.2 检查前置节点的状态,如果不是等待唤醒(SIGNAL),就把前置节点设置为SIGNAL,否则把当前节点的线程阻塞,
也就是说,第二次循环时,当前节点就被阻塞了。
2.2.3 线程被唤醒后,会继续执行2.2.1,得到获取锁的机会

/**
入口:获取锁,失败就阻塞
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} /**
* 构建了一个队列 对应2.1 1.入队:原子性的更新tail
2.出队:更新head * +------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
/**
自旋至阻塞的过程:对应2.2
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//检查头节点,对应2.2.1
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断前置节点的状态,并阻塞当前节点对应的线程 对应2.2.2
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

释放锁成功后:
3.1 拿到头节点的下一个节点
3.2 唤醒这个节点
3.3 被唤醒的节点走到2.2.3 然后走2.2.1

/**
释放锁的入口:成功后唤醒下一个节点
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
//对应3.1
Node h = head;
if (h != null && h.waitStatus != 0)
//对应3.2,唤醒之后,下一个节点结束阻塞,自旋获取锁进入2.2.1
unparkSuccessor(h);
return true;
}
return false;
}

小结:

从整个锁的获取和释放的流程来看,其基本的原理与CLH锁并没有太大区别:

1.尝试获取锁,失败则进入队列,然后自旋检查前置节点的状态
2.释放锁,同时保证某个后置节点能获取到锁并结束自旋

AQS(一) 对CLH队列的增强的更多相关文章

  1. 并发之AQS原理(二) CLH队列与Node解析

    并发之AQS原理(二) CLH队列与Node解析 1.CLH队列与Node节点 就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队. 一条队列是队列中每一个人 ...

  2. 从ReentrantLock实现非公平锁的源码理解AQS中的CLH队列

    虽然前面也看过AQS的文章,并且转载过一篇大佬的分析,但是我觉得他们对于AQS和ReentrantLock部分的源码的分析并不详细,自己理解期来还是有问题,于是自己准备花时间重新梳理下,好了,进入正题 ...

  3. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  4. 【Java并发编程实战】—– AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...

  5. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  6. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格依照FIFO的队列.他可以确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  7. 可重入锁 公平锁 读写锁、CLH队列、CLH队列锁、自旋锁、排队自旋锁、MCS锁、CLH锁

    1.可重入锁 如果锁具备可重入性,则称作为可重入锁. ========================================== (转)可重入和不可重入 2011-10-04 21:38 这 ...

  8. CLH队列锁

    http://blog.csdn.net/aesop_wubo/article/details/7533186 CLH锁即Craig, Landin, and Hagersten (CLH) lock ...

  9. Java 并发编程学习笔记 理解CLH队列锁算法

    CLH算法实现 CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁.结点之间是通过隐形的链表相连,之所以叫隐形的链 ...

随机推荐

  1. js- 引用和复制(传值和传址)

    js- 引用和复制(传值和传址) 好像一般很少人讲到js中的引用和复制,不过弄清楚这个概念可以帮助理解很多东西 先讲一下很基础的东西,看看js中几种数据类型分别传的什么引用:对象.数组.函数复制:数字 ...

  2. js 获取时间相关

    $(document).ready(function () {            var date = new Date();            var sb = "";  ...

  3. 跨页面传值之QueryString

    跨页面传值常用方法 1.QueryString 2.Form-post控件传递 3.Cookies传递 4.Application传递 5.Session传递(灵活强大) 1.query传值 http ...

  4. MySQL 查询多张表中相同字段的最大值

    MySql : 有N张表,N未知,每张表都有一个字段(id),每张表的字段结构不完全一样,如何查询所有表里面所有id的最大值?如下图所示: 对上面三张表进行操作的话,结果应该为:9 SQL语句: se ...

  5. re模块——正则表达式操作

    一.什么是正则? 正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法.或者说:正则就是用来描述一类事物的规则.(在Python中)它内嵌在Python中,并通过 r ...

  6. configparser模块——配置文档

    configparser模块用于生成和修改常见配置文档. 预制配置文件:conf.ini [DEFAULT] ServerAliveInterval = 45 Compression = yes Co ...

  7. Web前端面试指导(十):元素定位有哪些?

    本题点评 在web前端中,元素定位是必须掌握的,是网页制作的必备技能,也是衡量是否为一个合格的web前端开发的标准之一,在网页设计中,很多地方都需要使用定位,例如菜单弹出,提示信息层等都需要定位.所以 ...

  8. centos的nginx支持ssl

    首先看centos是否支持ssl 输入:openssl version 如无 则去 http://slproweb.com/products/Win32OpenSSL.html  寻找 生成私钥后面的 ...

  9. java面试题之----spring与struts2的比较

    我们从以下几个维度来区分两者的概念: 1.  机制:spring mvc的入口是servlet,而struts2是filter.     补充几点知识:     < Filter 实现javax ...

  10. sqlserver ceiling函数用法

    ceiling函数返回大于或等于所给数字表达式的最小整数. floor函数返回小于或等于所给数字表达式的最大整数. eg: select ceiling(4.42) ---5select CEILIN ...