【Java并发编程实战】----- AQS(四):CLH同步队列
在【Java并发编程实战】—–“J.U.C”:CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形。其主要从两方面进行了改造:节点的结构与节点等待机制。在结构上引入了头结点和尾节点,他们分别指向队列的头和尾,尝试获取锁、入队列、释放锁等实现都与头尾节点相关,并且每个节点都引入前驱节点和后后续节点的引用;在等待机制上由原来的自旋改成阻塞唤醒。其结构如下:
知道其结构了,我们再看看他的实现。在线程获取锁时会调用AQS的acquire()方法,该方法第一次尝试获取锁如果失败,会将该线程加入到CLH队列中:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
这是addWaiter()的实现,在厘清这段代码之前我们要先看一个更重要的东东,Node,CLH队列的节点。其源码如下:
static final class Node {
/** 线程已被取消 */
static final int CANCELLED = 1; /** 当前线程的后继线程需要被unpark(唤醒) */
static final int SIGNAL = -1; /** 线程(处在Condition休眠状态)在等待Condition唤醒 */
static final int CONDITION = -2; /** 共享锁 */
static final Node SHARED = new Node();
/** 独占锁 */
static final Node EXCLUSIVE = null; volatile int waitStatus; /** 前继节点 */
volatile Node prev; /** 后继节点 */
volatile Node next; volatile Thread thread; Node nextWaiter; final boolean isShared() {
return nextWaiter == SHARED;
} /** 获取前继节点 */
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
} /**
* 三个构造函数
*/
Node() {
} Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
} Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
在这个源代码中有三个值(CANCELLED、SIGNAL、CONDITION)要特别注意,前面提到过CLH队列的节点都有一个状态位,该状态位与线程状态密切相关:
CANCELLED = 1:因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态;
SIGNAL = -1:其后继节点已经被阻塞了,到时需要进行唤醒操作;
CONDITION = -2:表示这个结点在条件队列中,因为等待某个条件而被阻塞;
0:新建节点一般都为0。
入列
在线程尝试获取锁的时候,如果失败了需要将该线程加入到CLH队列,入列中的主要流程是:tail执行新建node,然后将node的后继节点指向旧tail值。注意在这个过程中有一个CAS操作,采用自旋方式直到成功为止。其代码如下:
for(;;){
Node t = tail;
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
其实这段代码在enq()方法中存在。
出列
当线程是否锁时,需要进行“出列”,出列的主要工作则是唤醒其后继节点(一般来说就是head节点),让所有线程有序地进行下去:
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
取消
线程因为超时或者中断涉及到取消的操作,如果某个节点被取消了,那个该节点就不会参与到锁竞争当中,它会等待GC回收。取消的主要过程是将取消状态的节点移除掉,移除的过程还是比较简单的。先将其状态设置为CANCELLED,然后将其前驱节点的pred执行其后继节点,当然这个过程仍然会是一个CAS操作:
node.waitStatus = Node.CANCELLED;
Node pred = node.prev;
Node predNext = pred.next;
Node next = node.next;
挂起
我们了解了AQS的CLH队列相比原始的CLH队列锁,它采用了一种变形操作,将自旋机制改为阻塞机制。当前线程将首先检测是否为头结点且尝试获取锁,如果当前节点为头结点并成功获取锁则直接返回,当前线程不进入阻塞,否则将当前线程阻塞:
for (;;) {
if (node.prev == head)
if(尝试获取锁成功){
head=node;
node.next=null;
return;
}
阻塞线程
}
参考
【Java并发编程实战】----- AQS(四):CLH同步队列的更多相关文章
- java并发编程实战《四》互斥锁(下)
互斥锁(下):如何用一把锁保护多个资源? 一把锁可以保护多个资源,但是不能用多把锁来保护一个资源. 那如何保护多个资源? 当我们要保护多个资源时,首先要区分这些资源是否存在关联关系. 如下代码 ...
- 【JAVA并发编程实战】3、同步容器
同步容器包括Vector和Hashtable,还有一些由Collections.synchronizedXxx等工厂方法创建的 1.同步容器类的问题 同步容器类都是线程安全的,但是有些时候还是要客户端 ...
- java并发编程(十四)同步问题的内存可见性
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17288243 加锁(synchronized同步)的功能不仅仅局限于互斥行为,同时还存在另 ...
- 【java并发编程实战】-----线程基本概念
学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...
- 【Java并发编程实战】—– AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...
- 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport
在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...
- 【死磕Java并发】-----J.U.C之AQS:CLH同步队列
此篇博客全部源代码均来自JDK 1.8 在上篇博客[死磕Java并发]-–J.U.C之AQS:AQS简单介绍中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列. CLH同步队列是一个F ...
- 【Java并发编程实战】----- AQS(二):获取锁、释放锁
上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...
- 【Java并发编程实战】-----“J.U.C”:CLH队列锁
在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...
随机推荐
- nginx配置反向代理或跳转出现400问题处理记录
午休完上班后,同事说测试站点访问接口出现400 Bad Request Request Header Or Cookie Too Large提示,心想还好是测试服务器出现问题,影响不大,不过也赶紧上 ...
- CYQ.Data、ASP.NET Aries 百家企业使用名单
如果您或您所在的公司正在使用此框架,请联系左侧的扣扣,告知我信息,我将为您添加链接: 以下内容为已反馈的用户,(收集始于:2016-08-08),仅展示99家: 序号 企业名称 企业网址 备注 1 山 ...
- Java 征途:行者的地图
前段时间应因缘梳理了下自己的 Java 知识体系, 成文一篇望能帮到即将走进或正在 Java 世界跋涉的程序员们. 第一张,基础图 大约在 2003 年我开始知道 Java 的(当时还在用 Delph ...
- 逆天通用水印支持Winform,WPF,Web,WP,Win10。支持位置选择(9个位置 ==》[X])
常用技能:http://www.cnblogs.com/dunitian/p/4822808.html#skill 逆天博客:http://dnt.dkil.net 逆天通用水印扩展篇~新增剪贴板系列 ...
- PHP数据类型之间的强制转换
1.实型数据强制转换为整型数据 $float1=2.7; $int1=(int)$float1; echo var_dump($int1),"<br>"; 输出: in ...
- javascript匹配各种括号书写是否正确
今天在codewars上做了一道题,如下 看上去就是验证三种括号各种嵌套是否正确书写,本来一头雾水,一种括号很容易判断, 但是三种怎么判断! 本人只是个前端菜鸟,,不会什么高深的正则之类的. 于是,在 ...
- echarts+php+mysql 绘图实例
最近在学习php+mysql,因为之前画图表都是直接在echart的实例demo中修改数据,便想着两相结合练习一下,通过ajax调用后台数据画图表. 我使用的是echart3,相比较第二版,echar ...
- bootstrap-fileinput 简单使用
bootstrap-fileinput 是一款图片/文件上传 bootstrap 插件,简单示例代码: <!DOCTYPE html> <html> <head> ...
- [原] 利用 OVS 建立 VxLAN 虚拟网络实验
OVS 配置 VxLAN HOST A ------------------------------------------ | zh-veth0(10.1.1.1) VM A | | ---|--- ...
- Html.DropDownLis绑定数据库
效果: 方法一: View: <div class="col-md-md-4"> <div class="input-group"> & ...