【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;
}
堵塞线程
}

參考

1、Java并发框架——AQS堵塞队列管理(二)

2、Java并发框架——AQS堵塞队列管理(三)

【Java并发编程实战】—– AQS(四):CLH同步队列的更多相关文章

  1. java并发编程实战《四》互斥锁(下)

    互斥锁(下):如何用一把锁保护多个资源?    一把锁可以保护多个资源,但是不能用多把锁来保护一个资源. 那如何保护多个资源? 当我们要保护多个资源时,首先要区分这些资源是否存在关联关系. 如下代码 ...

  2. 【JAVA并发编程实战】3、同步容器

    同步容器包括Vector和Hashtable,还有一些由Collections.synchronizedXxx等工厂方法创建的 1.同步容器类的问题 同步容器类都是线程安全的,但是有些时候还是要客户端 ...

  3. java并发编程(十四)同步问题的内存可见性

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17288243 加锁(synchronized同步)的功能不仅仅局限于互斥行为,同时还存在另 ...

  4. 【java并发编程实战】-----线程基本概念

    学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...

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

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

  6. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  7. 【死磕Java并发】-----J.U.C之AQS:CLH同步队列

    此篇博客全部源代码均来自JDK 1.8 在上篇博客[死磕Java并发]-–J.U.C之AQS:AQS简单介绍中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列. CLH同步队列是一个F ...

  8. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

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

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

随机推荐

  1. 涨知识 - II

    计算机网络相关 1.在无盘工作站向服务器申请IP地址时,使用的是(     )协议. A.ARP B.RARP C.ICMP D.IGMP 解析: ARP(地址解析协议)是设备通过自己知道的IP地址来 ...

  2. 关于处理移动端Vue单页面及其内嵌兼容问题

    关于处理移动端Vue单页面及其内嵌兼容问题 question:由于最近转移了以前的H5项目,重构使用Vue单页面,导致部分手机内嵌或在微信浏览器中无法浏览,或者无法使用ajax请求:手机机型千变万化, ...

  3. 元素属性的添加删除(原生js)

    添加属性 odiv.setAttribute("title","hello div!"); odiv.setAttribute("class" ...

  4. 【转】utf-8的中文是一个汉字占三个字节长度

    因为看到百度里面这个人回答比较生动,印象比较深刻,所以转过来做个笔记 原文链接 https://zhidao.baidu.com/question/1047887004693001899.html 知 ...

  5. Android RecyclerView使用 及 滑动时加载图片优化方案

    1.控制线程数量 + 数据分页加载2.重写onScrollStateChanged方法 这个我们后面再谈,下面先来看看RecyclerView控件的使用及我们为什么选择使用它 RecyclerView ...

  6. [转]Linux下/proc目录简介

    1. /proc目录Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构.改变内核设置的机制.proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间.它以文 ...

  7. C#入门经典 Chapter3 变量和表达式

    3.1 C#基本语法 分号结束语句 花括号字符不需要附带分号 缩进     注释:/*....*/,//,/// 区分大小写 3.2 C#控制台应用程序的基本结构 namespace Chapter3 ...

  8. PDO访问其他数据库操作及作用

    PDO的作用 PDO<!--数据访问抽象层--><!--1.可以访问其他数据库--><!--2.具有事务功能--><!--3.带有预处理语句功能(防止SQL注 ...

  9. UltraEdit(UE)window破解方法

      安装UltraEdit(一路下一步,无难点)成功后,打开软件弹出如下使用模式提示信息.   关掉UltraEdit软件,同时  断本机网络.重新打开UltraEdit软件:   点击[输入许可证密 ...

  10. CSS——盒子

    CSS中的盒子具有以下几个种重要的属性: 1.border(边框) :盒子的厚度 2.padding(内边距):盒子内容距离盒子边框的距离 3.margin(外边距):盒子边框与其他的盒子的距离