并发系列(3)之 CLH、MCS 队列锁简介

这篇博客主要是做为 AbstractQueuedSynchronizer 的背景知识介绍;平时接触也很是的少,若是你不感兴趣能够跳过;可是了解一下能更加的清楚 AQS 的设计思路;java

1、自旋锁简介

一般状况下解决多线程共享资源逻辑一致性问题有两种方式:node

  • 互斥锁:当发现资源被占用的时候,会阻塞本身直到资源解除占用,而后再次尝试获取;
  • 自旋锁:当发现占用时,一直尝试获取锁(线程没有被挂起的过程,也就没有线程调度切换的消耗);

对于这两种方式没有优劣之分,只有是否适合当前的场景;具体的对比就不在继续深刻了,若是你很感兴趣能够查看 《多处理器编程的艺术》 提取码:rznn ;web


可是若是竞争很是激烈的时候,使用自旋锁就会产生一些额外的问题:编程

  • 可能致使一些线程始终没法获取锁(争抢的时候必然是当前活跃线程得到锁的概率大),也就是饥饿现象;
  • 由于自旋锁会依赖一个共享的锁标识,因此竞争激烈的时候,锁标识的同步也须要消耗大量的资源;
  • 若是要用自旋锁实现公平锁(即先到先获取),此时就还须要额外的变量,也会比较麻烦;

解决这些问题其中的一种办法就是使用队列锁,简单来说就是让这些线程排队获取;下面咱们介绍经常使用的两种,即 CLH 锁MCS 锁多线程


2、CLH 锁

CLH 是 Craig、Landin 和 Hagersten 三位做者的缩写,具体内容在 《Building FIFO and Priority-Queuing Spin Locks from Atomic Swap》 论文中有详细介绍,你们能够自行查看;咱们 JDK 中 java.util.concurrent.locks.AbstractQueuedSynchronizer 就是根据 CLH 锁的变种实现的;架构

简单实现:ide

public class CLH implements Lock {
  private final ThreadLocal<Node> preNode = ThreadLocal.withInitial(() -> null);
  private final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);
  private final AtomicReference<Node> tail = new AtomicReference<>(new Node());

  private static class Node {
    private volatile boolean locked;
  }

  @Override
  public void lock() {
    final Node node = this.node.get();
    node.locked = true;
    Node pre = this.tail.getAndSet(node);
    this.preNode.set(pre);
    while (pre.locked) ;
  }

  @Override
  public void unlock() {
    final Node node = this.node.get();
    node.locked = false;
    this.node.set(this.preNode.get());
  }
}


clh


3、MCS 锁

一样 MCS 是 John M. Mellor-Crummey 和 Michael L. Scott 名字的缩写,具体内容能够在 《Algorithms for Scalable Synchronization on Shared-Memory Multiprocessors》 论文中查看;测试

简单实现:ui

public class MCS implements Lock {
  private final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);
  private final AtomicReference<Node> tail = new AtomicReference<>();

  private static class Node {
    private volatile boolean locked = false;
    private volatile Node next = null;
  }

  @Override
  public void lock() {
    Node node = this.node.get();
    node.locked = true;
    Node pre = tail.getAndSet(node);
    if (pre != null) {
      pre.next = node;
      while (node.locked) ;
    }
  }

  @Override
  public void unlock() {
    Node node = this.node.get();
    if (node.next == null) {
      if (tail.compareAndSet(node, null)) {
        return;
      }
      while (node.next == null) ;
    }
    node.next.locked = false;
    node.next = null;
  }
}


clh


总结

  • 以上的代码我已经测试过,你们能够直接拿下来自行实验;
  • CLH 锁和 MCS 锁区别主要有两点:1. 链表结构的区别;2. 自旋对象的区别,CLH 是在前驱节点上自旋,而 MCS 是在自身节点上自旋;这里第二点才是最重要的,主要体如今 SMP(Symmetric Multi-Processor)NUMA(Non-Uniform Memory Access) 不一样的处理器架构上;这里你们能够自行 Google;
相关文章
相关标签/搜索