NUMA与SMP

SMP(Symmetric Multi-Processor),即对称多处理器结构,指服务器中多个CPU对称工作,每个CPU访问内存地址所需时间相同。其主要特征是共享,包含对CPU,内存,I/O等进行共享。SMP的优点是能够保证内存一致性,缺点是这些共享的资源很可能成为性能瓶颈,随着CPU数量的增加,每个CPU都要访问相同的内存资源,可能导致内存访问冲突,可能会导致CPU资源的浪费。常用的PC机就属于这种。
NUMA(Non-Uniform
Memory Access)非一致存储访问,将CPU分为CPU模块,每个CPU模块由多个CPU组成,并且具有独立的本地内存、I/O槽口等,模块之间可以通过互联模块相互访问,访问本地内存的速度将远远高于访问远地内存(系统内其它节点的内存)的速度,这也是非一致存储访问NUMA的由来。NUMA优点是可以较好地解决原来SMP系统的扩展问题,缺点是由于访问远地内存的延时远远超过本地内存,因此当CPU数量增加时,系统性能无法线性增加。

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()操作来实现的,它能够保证以原子方式更新对象引用。
  1. public class CLHLock implements Lock {
  2. AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());
  3. ThreadLocal<QNode> myPred;
  4. ThreadLocal<QNode> myNode;
  5. public CLHLock() {
  6. tail = new AtomicReference<QNode>(new QNode());
  7. myNode = new ThreadLocal<QNode>() {
  8. protected QNode initialValue() {
  9. return new QNode();
  10. }
  11. };
  12. myPred = new ThreadLocal<QNode>() {
  13. protected QNode initialValue() {
  14. return null;
  15. }
  16. };
  17. }
  18. @Override
  19. public void lock() {
  20. QNode qnode = myNode.get();
  21. qnode.locked = true;
  22. QNode pred = tail.getAndSet(qnode);
  23. myPred.set(pred);
  24. while (pred.locked) {
  25. }
  26. }
  27. @Override
  28. public void unlock() {
  29. QNode qnode = myNode.get();
  30. qnode.locked = false;
  31. myNode.set(myPred.get());
  32. }
  33. }

从代码中可以看出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队列锁。

参考资料:

The Art of Multiprocessor Programming

JAVA并发编程学习笔记之CLH队列锁的更多相关文章

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

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

  2. Java并发编程学习笔记

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. NFS挂载时出现mount

    NFS挂载时出现"mount.nfs: access denied by server while mounting "的解决方法 1.使用了非法端口,也就是使用了大于1024的端 ...

  2. Effective C++——Item33: 避免隐藏继承的名字

    Effective C++--Item33: 避免隐藏继承的名字 一.从原理理解隐藏 从变量作用域看隐藏 全局变量x和局部变量x的类型是不同的,但C++的隐藏规则:只隐藏名字(hiding names ...

  3. 强!70.3K star ! 推荐一款功能强大、开源、可视化的性能实时监控系统:Netdata

    在当今复杂多变的IT环境中,系统性能的实时监控与分析对于确保业务连续性.系统稳定运行以及快速故障排查至关重要.随着云计算.大数据和微服务架构的普及,对监控系统的要求也日益增高. 今天给大家推荐一款性能 ...

  4. TextIn文档树引擎,助力RAG知识库问答检索召回能力提升

    ​ TextIn团队的文档解析测评工具Markdown Tester在Github发布后,我们陆续与大家探讨了目前业内对PDF解析工作的评判标准与我们各项测评指标的设计原理,包括段落.表格.公式.阅读 ...

  5. CSS – Selector

    前言 这篇记入一些我常用到. 以前写的笔记 css 选择器 (学习笔记) Whatever (*) * {} By Id (#) #id {} By Class (.) .class-name {} ...

  6. Spirng Aop 实现自定义注解及实现

    需求:日志记录 需要记录当前用户访问的每个接口对应的前端页面功能信息 声明一个注解 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({E ...

  7. 【赵渝强老师】Docker Swarm集群的数据持久化

    如果Docker Swarm集群中运行了mysql.nginx等服务,这些服务的数据如果没有挂载到宿主机中,那么容器一旦停止运行,那就意味着数据丢失. 有什么方法可以解决swarm集群中运行的服务能够 ...

  8. 6款支持C#语言的AI辅助编程工具,开发效率提升利器!

    前言 在这个AI迅速发展的阶段,涌现出了一大批好用的AI辅助编程工具.AI辅助编程工具能够提高开发效率.改善代码质量.降低bug率,是现代软件开发过程中的重要助手.今天大姚给大家分享6款AI辅助编程工 ...

  9. markdown的html优雅使用语法(2024/10/10guixiang原创)

    一:图片部分 第一范式 图 2 全字段排序 <center> <img style="border-radius: 0.3125em; box-shadow: 0 2px ...

  10. 015 Python 的输入输出和字符串格式化(终于可以和计算机交流了)

    #!/usr/bin/env python # -*- coding:utf-8 -*- # Datatime:2022/7/26 20:11 # Filename:015 Python 的输入输出和 ...