AQS、CLS、MCS


CLH锁和MCS锁。


CLH锁适用于SMP(symmetric Multi-processing)对称多处理器结构。
MCS锁使用于NUMA(non-Uniform Memory Access)非一致存储访问。

SMP:一台服务器,多个CPU,但多个CPU共享一块内存。每个CPU读取内存的时间一致。
NUMA:一台服务器,多个CPU,但每个CPU有独立的内存,多个CPU通过互联模块相互访问。分为本地内存,和远地内存。

CLH:

CLHLock 是一个自旋锁,能确保无饥饿性,能保证FIFO的顺序来获取资源。

排队取钱:
A过来排队,发现前面没人,就直接到窗口办理了。
这时候B来了,排在了A的后面,发现A还没弄完,那就自己玩玩手机,时不时看看A搞完了。
这时候A搞完了,B看到A搞完了,就来到窗口前办理。


CLHLock的对象中只有一个tail,然后两个ThreadLocal 表示当前的node和前节点,myNode和myPred
方法也只有lock和unlock两个方法。

其中ThreadLocal 保存的对象是QNode,QNode就更简单了,只有一个locked,是volatile。
类图如下:

AQS、CLS、MCS_第1张图片

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

CLHLock的操作就跟这个类方法数量一样,一个初始化,一个lock,一个unlock。
      1. 初始化:
    1.1 新建tail,指向一个new的QNode
    1.2 myPred指向null
    1.3 myNode,指向一个new的QNode
2. 当有一个A线程来调用lock的时候。
    2.1 获取当前ThreadLocal变量myNode,如果为空则指向一个new的QNode
    2.2 获取tail指向的node,设置为myPred
    2.3 tail指向当前myNode
    2.4 如果前驱节点的locked为true,则自旋
3. 当A线程unlock的时候
    3.1 获取当前TheadLocal变量myNode
    3.2 myNode.locked = false
    3.3 myNode指向myPred

具体图如下:

AQS、CLS、MCS_第2张图片

AQS、CLS、MCS_第3张图片AQS、CLS、MCS_第4张图片 AQS、CLS、MCS_第5张图片 

实现代码如下:

public class CLHLock implements Lock {  
    AtomicReference tail = new AtomicReference(new QNode());  
    ThreadLocal myPred;  
    ThreadLocal myNode;  
  
    public CLHLock() {  
        tail = new AtomicReference(new QNode());  
        myNode = new ThreadLocal() {  
            protected QNode initialValue() {  
                return new QNode();  
            }  
        };  
        myPred = new ThreadLocal() {  
            protected QNode initialValue() {  
                return null;  
            }  
        };  
    }  
  
    @Override  
    public void lock() {  
        QNode qnode = myNode.get();  
        qnode.locked = true;  
        QNode pred = tail.getAndSet(qnode);  
        myPred.set(pred);  
        while (pred.locked) {  
        }  
    }  
  
    @Override  
    public void unlock() {  
        QNode qnode = myNode.get();  
        qnode.locked = false;  
        myNode.set(myPred.get());  
    }  
}


MCS:

CLH在SMP效果比较好,但是如果是NUMA中不同的CPU内存不同,有可能会因为前驱节点所在的内存获取耗时较长,会导致性能较差。
所以NUMA中更好的实现是SMP。

依然还是排队:
1. A来排队,没有人,直接取钱
2. B来排队了,发现有人,就跟A说,你后面是我哈,等会搞完了叫我
3. A取完了,喊了一下B,B继续取

MCS 跟CLH很类似,区别在于CLH是根据前驱节点的locked自旋,而MCS是根据本地节点的locked自旋。所以,不需要频繁访问远处内存。

AQS、CLS、MCS_第6张图片

MCSLock只有两个方法,lock和unlock。
1. A来lock了
    1.1 获取A线程的myNode,没有就创建
    1.2 获取tail的节点作为前驱节点
    1.3 如果前驱节点不为空,则当前节点locked改为true,前驱节点的next指向当前节点。根据当前node的locked自旋
    1.4 如果前驱节点为空,不需要自旋
2. B也来lock了。
    2.1 获取B线程的myNode,没有就创建
    2.2 获取tail的节点作为前驱节点
    2.3 改A的next指向B的node,当前节点locked改为true,自旋等待
3. A准备release了
    3.1 获取A的myNode
    3.2 如果A的后继节点为空,则把tail从myNode改为null
    3.3 如果tail不是myNode,或者其他情况改不成功,有可能是有线程来排队了,已经加到了tail,还没有append到next来。则自旋等待后继节点append
    3.4 如果A的后继节点不为空,则把后继节点的lock改成false,把当前next设置为null代码如下:

public class MCSLock implements Lock {
    AtomicReference tail;
    ThreadLocal myNode;

    @Override
    public void lock() {
        QNode qnode = myNode.get();
        QNode pred = tail.getAndSet(qnode);
        if (pred != null) {
            qnode.locked = true;
            pred.next = qnode;

            // wait until predecessor gives up the lock
            while (qnode.locked) {
            }
        }
    }

    @Override
    public void unlock() {
        QNode qnode = myNode.get();
        if (qnode.next == null) {
            if (tail.compareAndSet(qnode, null))
                return;
            
            // wait until predecessor fills in its next field
            while (qnode.next == null) {
            }
        }
        qnode.next.locked = false;
        qnode.next = null;
    }

    class QNode {
        boolean locked = false;
        QNode next = null;
    }


}

你可能感兴趣的:(并发)