CLH算法实现

CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁。结点之间是通过隐形的链表相连,之所以叫隐形的链表是因为这些结点之间没有明显的next指针,而是通过myPred所指向的结点的变化情况来影响myNode的行为。CLHLock上还有一个尾指针,始终指向队列的最后一个结点。CLHLock的类图如下所示:
 
 
 
当一个线程需要获取锁时,会创建一个新的QNode,将其中的locked设置为true表示需要获取锁,然后线程对tail域调用getAndSet方法,使自己成为队列的尾部,同时获取一个指向其前趋的引用myPred,然后该线程就在前趋结点的locked字段上旋转,直到前趋结点释放锁。当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前趋结点。如下图所示,线程A需要获取锁,其myNode域为true,些时tail指向线程A的结点,然后线程B也加入到线程A后面,tail指向线程B的结点。然后线程A和B都在它的myPred域上旋转,一量它的myPred结点的locked字段变为false,它就可以获取锁扫行。明显线程A的myPred locked域为false,此时线程A获取到了锁。
 
 
 
 
 
整个CLH的代码如下,其中用到了ThreadLocal类,将QNode绑定到每一个线程上,同时用到了AtomicReference,对尾指针的修改正是调用它的getAndSet()操作来实现的,它能够保证以原子方式更新对象引用。
 
 public class CLHLock {  

     AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());
ThreadLocal<QNode> myPred;
ThreadLocal<QNode> myNode; public static class QNode {
//注意这个地方 如果不加volatile则会导致线程永远死循环
//关于volatile的用法在我的另外一篇文章 http://www.cnblogs.com/daxin/p/3364014.html
public volatile boolean locked = false;
} public CLHLock() {
myNode = new ThreadLocal<QNode>() {
protected QNode initialValue() {
return new QNode();
}
};
myPred = new ThreadLocal<QNode>() {
protected QNode initialValue() {
return null;
}
};
} public void lock() {
QNode qnode = myNode.get();
qnode.locked = true;
QNode pred = tail.getAndSet(qnode);
myPred.set(pred);
while (pred.locked) {
//非阻塞算法
}
} public void unlock() {
QNode qnode = myNode.get();
qnode.locked = false;
myNode.set(myPred.get());
}
}
 
从代码中可以看出lock方法中有一个while循环,这 是在等待前趋结点的locked域变为false,这是一个自旋等待的过程。unlock方法很简单,只需要将自己的locked域设置为false即可。

CLH优缺点

CLH队列锁的优点是空间复杂度低(如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个myNode,L个锁有L个tail),CLH的一种变体被应用在了JAVA并发框架中。唯一的缺点是在NUMA系统结构下性能很差,在这种系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能将大打折扣,但是在SMP系统结构下该法还是非常有效的。一种解决NUMA系统结构的思路是MCS队列锁。

Java 并发编程学习笔记 理解CLH队列锁算法的更多相关文章

  1. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  2. Java并发编程学习笔记 深入理解volatile关键字的作用

    引言:以前只是看过介绍volatile的文章,对其的理解也只是停留在理论的层面上,由于最近在项目当中用到了关于并发方面的技术,所以下定决心深入研究一下java并发方面的知识.网上关于volatile的 ...

  3. Java并发编程学习:线程安全与锁优化

    本文参考<深入理解java虚拟机第二版> 一.什么是线程安全? 这里我借<Java Concurrency In Practice>里面的话:当多个线程访问一个对象,如果不考虑 ...

  4. Java并发编程学习笔记(一)——线程安全性

    主要概念:线程安全性.原子性.原子变量.原子操作.竟态条件.复合操作.加锁机制.重入.活跃性与性能. 1.当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变 ...

  5. JAVA并发编程学习笔记------协作对象之间发生的死锁

    一. 如果在持有锁时调用某个外部方法,那么将出现活跃性问题.在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁.如下代码: public c ...

  6. [转]JAVA并发编程学习笔记之Unsafe类

    1.通过Unsafe类可以分配内存,可以释放内存:类中提供的3个本地方法allocateMemory.reallocateMemory.freeMemory分别用于分配内存,扩充内存和释放内存,与C语 ...

  7. JAVA并发编程学习笔记------多线程调优

    1. 多线程场景下尽量使用并发容器代替同步容器 (如ConcurrentHashMap代替同步且基于散列的Map, 遍历操作为主要操作的情况下用CopyOnWriteArrayList代替同步的Lis ...

  8. Java并发编程学习笔记(三)——对象的组合

    重要概念: 1.在设计线程安全类的过程中,需要包含以下三个基本要素: (1)找出构成对象状态的所有变量. (2)找出约束状态变量的不变性条件. (3)建立对象状态的并发访问管理策略. 2.

  9. Java并发编程学习笔记(二)——对象的共享

    主要概念:可见性.重排序.失效数据.最低安全性.发布.逸出.线程封闭(Ad-hoc.栈封闭.ThreadLocal类).不变性.Final域.事实不可变对象. 1.在没有同步的情况下,编译器.处理器以 ...

随机推荐

  1. 【LeetCode】136. Single Number (4 solutions)

    Single Number Given an array of integers, every element appears twice except for one. Find that sing ...

  2. Chrome浏览器桌面通知提示设置

    版本 24.0.1312.56 m     老版本23.* 桌面通知,也可以由用户在Chrome浏览器中自定义:板手 -> 选项  -> 高级选项 –> 通知 (管理例外情况…).

  3. Linux常见的进程调度算法

    进程调度:在操作系统中调度是指一种资源分配. 调度算法是指: 根据系统的资源分配策略所规定的资源分配算法. 操作系统管理了系统的有限资源,当有多个进程(或多个进程发出的请求)要使用这些资源时,因为资源 ...

  4. Spring注解方式配置说明

    1.<context:annotation-config/>与<context:component-scan base-package=”XX.XX”/> 在基于主机方式配置S ...

  5. Vim进阶技术:搜索和替换

    行内搜索 行内搜索,也就是在当前行内进行搜索和移动,通常都与编辑命令一起使用. fx -- 移动到下一个字符x的位置,光标停留在x字符上面 tx -- 移动到下一个字符x的位置,光标停留在x前一个字符 ...

  6. laravel中间件简单使用

    laravel内置了一个中间件来验证用户是否经过认证,如果用户没有经过认证,中间件会将用户重定向到登录页面,否则如果用户经过认证,中间件就会允许请求继续往前进入下一步操作. 当然,除了认证之外,中间件 ...

  7. 修改easyui的easyloader的默认css目录路径

    easyloader默认情况下会使用js文件所在目录下的themes文件夹中的css,这里改成项目自定义的css文件夹. 首先找到: var m=src.match(/easyloader\.js(\ ...

  8. CSS控制当鼠标滑过时更换图片的效果

    鼠标滑过时更换图片的效果有很多方法可以实现,在本文将为大家介绍喜爱如何通过css来实现.<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tra ...

  9. 用javascript获得地址栏参数的两种方法

    javascript获得地址栏参数. 方法1: <script language="JavaScript"> //取地址栏参数 <!-- function Req ...

  10. 【Android】6.3 ProgressDialog

    分类:C#.Android.VS2015: 创建日期:2016-02-08 一.简介 进度条对话框(ProgressDialog)常用于不能在短时间内快速完成的操作,显示进度条的目的是为了让用户明白程 ...