JAVA并发编程学习笔记之MCS队列锁

来源:互联网 发布:fireworks mac版 编辑:程序博客网 时间:2024/06/06 00:09

简介

与CLH类似,MCS也是由QNode对象构成的链表,每个QNode表示一个锁持有者,表示一个线程要么已经获取锁,要么正在等待锁。它与CLH不同的是,队列是一个显示链表,是通过next指针串起来的。



实现

MCS队列锁的具体实现如下:

1、如图(a)所示,队列初始化时没有结点,tail=null;

2、如图(b)所示,线程A想要获取锁,于是将自己置于队尾,由于它是第一个结点,它的locked域为false;;

3、如果(c)所示,线程B和C相继加入队列,前面说了这个队列是由next指针串起来的,所以a->next=b,b->next=c。且B和C现在没有获取锁,处于等待状态,所以它们的locked域为true,尾指针指向线程C对应的结点;

4、如果(d)所示,线程A释放锁后,顺着它的next指针找到了线程B,并把B的locked域设置为false。这一动作会触发线程B获取锁。

从上面的实现可以看出,MSC与CLH最大的不同并不是链表是显示还是隐式,而是线程自旋的规则不同,CLH是在前趋结点的locked域上自旋等待,而MSC是在自己的结点的locked域上自旋等待。正因为如此,它解决了CLH在NUMA系统架构中获取locked域状态内存过远的问题。

下面看看MCS队列锁的JAVA实现:

[java] view plaincopyprint?
  1. public class MCSLock implements Lock {  
  2.     AtomicReference<QNode> tail;  
  3.     ThreadLocal<QNode> myNode;  
  4.   
  5.     @Override  
  6.     public void lock() {  
  7.         QNode qnode = myNode.get();  
  8.         QNode pred = tail.getAndSet(qnode);  
  9.         if (pred != null) {  
  10.             qnode.locked = true;  
  11.             pred.next = qnode;  
  12.   
  13.             // wait until predecessor gives up the lock  
  14.             while (qnode.locked) {  
  15.             }  
  16.         }  
  17.     }  
  18.   
  19.     @Override  
  20.     public void unlock() {  
  21.         QNode qnode = myNode.get();  
  22.         if (qnode.next == null) {  
  23.             if (tail.compareAndSet(qnode, null))  
  24.                 return;  
  25.               
  26.             // wait until predecessor fills in its next field  
  27.             while (qnode.next == null) {  
  28.             }  
  29.         }  
  30.         qnode.next.locked = false;  
  31.         qnode.next = null;  
  32.     }  
  33.   
  34.     class QNode {  
  35.         boolean locked = false;  
  36.         QNode next = null;  
  37.     }  
  38. }  
lock方法:

若要获得锁,线程会把自己的结点放到队列的尾部,如果队列中开始有结点,就将前一个结点的next结点指向当前结点;

然后就在自己的locked域上自旋等待,直到它的前趋结点把自己的locked设置为false为止。

unlock方法:

若要释放锁,先检查自己的next域是否为null,如果为null,要么当前结点是尾结点,要么还有其他线程正在争用锁。不管是哪种情况都可以采用compareAndSet(q,null)来判断,其中q为当前结点,如果调用成功,则没有其他线程在争用锁,于是将tail设置为null返回;如果调用失败,说明另一个比较慢的线程正在试图获得锁,于是自旋等待它结束。在以上任一种情况,一旦出现有后继结点就将后续结点的locked域设置为false,然后返回。


疑点

对于unlock方法,有人会问既然qnode.next==null,说明qnode是尾结点,那么compareAndSet(q,null)为什么会失败呢?

如下图(a)所示,开始只有线程A在获取锁,A确实是队尾元素,tail指针也指向了它,多线程环境下,一切皆有可能,就在准备进行compareAndSet(q,null)操作时,突然以迅雷不及掩耳之势闯入两个线程B和C,如图(b)所示,这时如果再进行compareAndSet(q,null)操作就会失败。不过在这种情况下,while(qnode.next==null)会跳出循环,紧接着执行下面的两句代码:

[java] view plaincopyprint?
  1. qnode.next.locked = false;  
  2. qnode.next = null;  


可见,释放锁操作在有线程闯入时也是能够正常工作的。


优缺点:

优点是适用于NUMA系统架构,缺点是释放锁也需要自旋等待,且比CLH读、写、CAS等操作调用次数多。


参考资料:

A simple correctness proof of the MCS contention-free lock

Algorithms for Scalable Synchronization on Shared-Memory Multiprocessors

高性能自旋锁 MCS Spinlock 的设计与实现

The Art of Multiprocessor Programming


来源:http://blog.csdn.net/aesop_wubo/article/details/7538934

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 淘宝买虚拟产品被骗了怎么办 哈尔滨暖气低于十八度怎么办 淘客店铺没人买怎么办 淘宝商家不给退货怎么办 淘宝卖家拒绝退款申请怎么办 运费险赔付少了怎么办 买了运费险退货怎么办 卖家运费险退货怎么办 京东生鲜有坏的怎么办 与上级意见不一致时你将怎么办 物金所倒闭投资怎么办 电商平台欺骗客户怎么办 pdf电脑打开是乱码怎么办 excel表格打开是乱码怎么办 win10安装软件出现乱码怎么办 华为手机速度越来越慢怎么办 oppo手机速度越来越慢怎么办 安卓手机速度越来越慢怎么办 青桔单车忘了锁怎么办 华为手机反应太慢了怎么办 魅族关机键失灵怎么办 oppa79手机开不开机怎么办 黑衣服洗完发白怎么办 白衣服被黑衣服染色了怎么办 评职称单位领导不推荐怎么办 支付宝被限制收款怎么办 在淘宝上下单想写两个地址怎么办 注销了的支付宝怎么办 狗狗黑色毛发红怎么办 蘑菇街直播间被禁言了怎么办 收了发票不付款怎么办 退款要先收发票怎么办 淘宝退款了又收到货怎么办 商家收货后拒绝退款怎么办 申请退货退款卖家不处理怎么办 淘宝买东西换货卖家不发货怎么办 淘宝自动默认付款没发货怎么办 支付宝支付失败可钱扣了怎么办 苹果nfc感应坏了怎么办 老鼠添过的盘子怎么办 ie浏览器页面显示网页错误怎么办