转载自:http://coderbee.net/index.php/concurrent/20131115/577

自旋锁(Spin lock)

自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。

简单的实现

  1. import java.util.concurrent.atomic.AtomicReference;
  2. public class SpinLock {
  3. private AtomicReference<Thread> owner = new AtomicReference<Thread>();
  4. public void lock() {
  5. Thread currentThread = Thread.currentThread();
  6. // 如果锁未被占用,则设置当前线程为锁的拥有者
  7. while (owner.compareAndSet(null, currentThread)) {
  8. }
  9. }
  10. public void unlock() {
  11. Thread currentThread = Thread.currentThread();
  12. // 只有锁的拥有者才能释放锁
  13. owner.compareAndSet(currentThread, null);
  14. }
  15. }

SimpleSpinLock里有一个owner属性持有锁当前拥有者的线程的引用,如果该引用为null,则表示锁未被占用,不为null则被占用。

这里用AtomicReference是为了使用它的原子性的compareAndSet方法(CAS操作),解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态。

缺点

  1. CAS操作需要硬件的配合;
  2. 保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重;
  3. 没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁。

Ticket Lock

Ticket Lock 是为了解决上面的公平性问题,类似于现实中银行柜台的排队叫号:锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。

当线程释放锁时,将服务号加1,这样下一个线程看到这个变化,就退出自旋。

简单的实现

  1. import java.util.concurrent.atomic.AtomicInteger;
  2. public class TicketLock {
  3. private AtomicInteger serviceNum = new AtomicInteger(); // 服务号
  4. private AtomicInteger ticketNum = new AtomicInteger(); // 排队号
  5. public int lock() {
  6. // 首先原子性地获得一个排队号
  7. int myTicketNum = ticketNum.getAndIncrement();
  8. // 只要当前服务号不是自己的就不断轮询
  9. while (serviceNum.get() != myTicketNum) {
  10. }
  11. return myTicketNum;
  12. }
  13. public void unlock(int myTicket) {
  14. // 只有当前线程拥有者才能释放锁
  15. int next = myTicket + 1;
  16. serviceNum.compareAndSet(myTicket, next);
  17. }
  18. }

缺点

Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。

下面介绍的CLH锁和MCS锁都是为了解决这个问题的。

MCS 来自于其发明人名字的首字母: John Mellor-Crummey和Michael Scott。

CLH的发明人是:Craig,Landin and Hagersten。

MCS锁

MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。

  1. import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
  2. public class MCSLock {
  3. public static class MCSNode {
  4. MCSNode next;
  5. boolean isLocked = true; // 默认是在等待锁
  6. }
  7. volatile MCSNode queue ;// 指向最后一个申请锁的MCSNode
  8. private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater
  9. . newUpdater(MCSLock.class, MCSNode. class, "queue" );
  10. public void lock(MCSNode currentThread) {
  11. MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1
  12. if (predecessor != null) {
  13. predecessor.next = currentThread;// step 2
  14. while (currentThread.isLocked ) {// step 3
  15. }
  16. }
  17. }
  18. public void unlock(MCSNode currentThread) {
  19. if ( UPDATER.get( this ) == currentThread) {// 锁拥有者进行释放锁才有意义
  20. if (currentThread.next == null) {// 检查是否有人排在自己后面
  21. if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4
  22. // compareAndSet返回true表示确实没有人排在自己后面
  23. return;
  24. } else {
  25. // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
  26. // 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完
  27. while (currentThread.next == null) { // step 5
  28. }
  29. }
  30. }
  31. currentThread.next.isLocked = false;
  32. currentThread.next = null;// for GC
  33. }
  34. }
  35. }

CLH锁

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

  1. import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
  2. public class CLHLock {
  3. public static class CLHNode {
  4. private boolean isLocked = true; // 默认是在等待锁
  5. }
  6. @SuppressWarnings("unused" )
  7. private volatile CLHNode tail ;
  8. private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater
  9. . newUpdater(CLHLock.class, CLHNode .class , "tail" );
  10. public void lock(CLHNode currentThread) {
  11. CLHNode preNode = UPDATER.getAndSet( this, currentThread);
  12. if(preNode != null) {//已有线程占用了锁,进入自旋
  13. while(preNode.isLocked ) {
  14. }
  15. }
  16. }
  17. public void unlock(CLHNode currentThread) {
  18. // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。
  19. if (!UPDATER .compareAndSet(this, currentThread, null)) {
  20. // 还有后续线程
  21. currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋
  22. }
  23. }
  24. }

CLH锁 与 MCS锁 的比较

下图是CLH锁和MCS锁队列图示: 

差异:

  1. 从代码实现来看,CLH比MCS要简单得多。
  2. 从自旋的条件来看,CLH是在本地变量上自旋,MCS是自旋在其他对象的属性。
  3. 从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
  4. CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。

注意:这里实现的锁都是独占的,且不能重入的。

自旋锁、排队自旋锁、MCS锁、CLH锁的更多相关文章

  1. JUC 并发编程--12, 使用AtomicInteger 实现一把锁(排队自旋锁), 代码演示

    前面 使用自旋锁实现了一把锁,(请看 第5篇) volatile 三大特性: 可见性, 不保证原子性, 禁止指令重排 为了解决 volatile不保证原子性的问题, 引入了原子类, AtomicInt ...

  2. 并发编程——详解 AQS CLH 锁

    从 acquire 方法开始 -- 获取 为什么 AQS 需要一个虚拟 head 节点 reelase 方法如何释放锁 总结 前言 AQS 是 JUC 中的核心,其中封装了资源的获取和释放,在我们之前 ...

  3. 可重入锁 公平锁 读写锁、CLH队列、CLH队列锁、自旋锁、排队自旋锁、MCS锁、CLH锁

    1.可重入锁 如果锁具备可重入性,则称作为可重入锁. ========================================== (转)可重入和不可重入 2011-10-04 21:38 这 ...

  4. Java锁之自旋锁详解

    锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类 ...

  5. CLH锁 、MCS锁

    一.引文 1.1 SMP(Symmetric Multi-Processor) 对称多处理器结构,指服务器中多个CPU对称工作,每个CPU访问内存地址所需时间相同.其主要特征是共享,包含对CPU,内存 ...

  6. MCS锁和CLH锁

    CLH锁:自旋锁,在上一个节点上等待,先上代码: public class CLHLock { /** * 保证原子性操作 * */ private AtomicReference<Node&g ...

  7. synchronized底层实现原理&CAS操作&偏向锁、轻量级锁,重量级锁、自旋锁、自适应自旋锁、锁消除、锁粗化

    进入时:monitorenter 每个对象有一个监视器锁(monitor).当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:1 ...

  8. 读书摘要:第七章 闩Suan锁和自旋锁

    摘要: 1.闩锁就像是内存上的锁,随着越来越多的线程参与进来,他们争相访问同一块内存,导致堵塞.2.自旋锁就是闩锁,不同之处是如果访问的内存不可用,它将继续检查轮询一段时间.3.拴锁和自旋锁是我们无法 ...

  9. JVM中锁优化,偏向锁、自旋锁、锁消除、锁膨胀

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt364 本文将简单介绍HotSpot虚拟机中用到的锁优化技术. 自旋锁 互斥同 ...

随机推荐

  1. python2.7练习小例子(二十五)

        25):题目:有5个人坐在一起,问第五个人多少岁?他说比第4个人大2岁.问第4个人岁数,他说比第3个人大2岁.问第三个人,又说比第2人大两岁.问第2个人,说比第一个人大两岁.最后问第一个人,他 ...

  2. python2.7练习小例子(九)

        9)1.题目:暂停一秒输出.     程序分析:使用 time 模块的 sleep() 函数.     程序源代码: #!/usr/bin/python # -*- coding: UTF-8 ...

  3. Java - 问题集 - 导出csv文件中文乱码

    微软的excel文件需要通过文件头的bom来识别编码,所以写文件时,需要先写入bom头. FileOutputStream fos = new FileOutputStream(new File(&q ...

  4. ibatis常用sql

    (1) 输入参数为单个值 <delete id="com.fashionfree.stat.accesslog.deleteMemberAccessLogsBefore" p ...

  5. 第三篇 Python执行方式和变量初始

    第一个Python程序 可以打开notepad或者其他文本编辑器,输入:print("Hello Python!"),将文件保存到任意盘符下,后缀名是  .py 两种python程 ...

  6. 开发react的一些记录

    1.keyboard事件返回的对象SyntheticKeyboardEvent全部是null 解决方法:SyntheticKeyboardEvent的type,which,timeStamp可以得到你 ...

  7. 基于HTML5移动web应用

    一.基于HTML5移动web应用 1.canvas 绘图 2.多媒体 3.本地存储 4.离线应用 5.使用地理位置 6.移动web框架   二.具体说明 1.HTML5标准最大的变化就是支持Web绘图 ...

  8. Mysql字符串截取:Left()、Right()、Substring()、Substring_index()

    在实际的项目开发中有时会有对数据库某字段截取部分的需求,这种场景有时直接通过数据库操作来实现比通过代码实现要更方便快捷些, mysql有很多字符串函数可以用来处理这些需求,如Mysql字符串截取总结: ...

  9. HTML如何给table添加滚动条

    HTML如何给table添加滚动条 要给table添加滚动条其实很简单,主要是给table放到一个div里去,然后再设置div显示滚动条即可.示例代码如下所示: <!--div比table大小要 ...

  10. 图书 Framework 设计指南: 可重用 .NET 库的约定、惯用法和模式 引出资料

    文章:框架设计准则     --微软 地址:https://docs.microsoft.com/zh-cn/dotnet/standard/design-guidelines/index