JAVA多线程之——自旋锁、CLH、MCS

自旋锁

学习了解自旋锁之前先回顾一下互斥锁
互斥锁
线程在获取互斥锁的时候,如果发现锁已经被其它线程占有,那么线程就会惊醒休眠,然后在适当的时机(比如唤醒)在获取锁。
自旋锁
那么自旋锁顾名思义就是“自旋”。就是当一个线程在尝试获取锁失败之后,线程不会休眠或者挂起,而是一直在循环检测锁是否被其它线程释放。
区别
互斥锁就是开始开销要大于自旋锁。临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。
适用的情况
互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

  • 临界区有IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈
  • 单核处理器
    自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。当递归调用时有可能造成死锁。
    线程(节点)队列
    了解了自旋锁之后,在学习ReentrantLock的时候,一个线程在等待锁的时候会被封装成一个Node节点,然后加入一个队列中并检测前一个节点是否是头节点,并且尝试获取锁,如果获取锁成功就返回,否则就阻塞。直到上一个节点释放锁并唤醒它。这样看来似乎跟自旋没什么挂钩。这是因为AQS里面的CLH队列是CLH队列锁的一种变形。先来了解一下CLH队列锁

CLH队列锁
CLH(Craig, Landin, and Hagersten locks): 是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。
CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。http://www.2cto.com/kf/201412/363574.html这篇文章中有比较详细的图解。
AQS中的CLH队列
了解了自旋锁与CLH队列锁之后,在学习AQS中的CLH队列就比较简单了。AQS中的CLH队列主要是对CLH队列锁改动了两个地方
1.节点结构上做出改变。CLH队列锁的节点包含一个布尔类型locked的字段。如果要获取锁,就将这个locked设置为true。然后就不停的轮训前驱节点的locked是否释放了锁(这个过程我们就叫做自旋)。AQS的CLH队列在结构上引入了头节点,尾节点。并且拥有一个前节点与下一个节点的引用。
2.在等待获取锁的机制上由自旋改成了等待阻塞。具体实现在ReentrantLock:http://blog.csdn.net/pengdandezhi/article/details/67082171中做了分析
MCS
MSC与CLH最大的不同并不是链表是显示还是隐式,而是线程自旋的规则不同:CLH是在前趋结点的locked域上自旋等待,而MSC是在自己的
结点的locked域上自旋等待。正因为如此,它解决了CLH在NUMA系统架构中获取locked域状态内存过远的问题。

    原文作者:java锁
    原文地址: https://blog.csdn.net/pengdandezhi/article/details/67672077
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞