概述
上一篇分析了CLH锁,这篇分析一下MCS锁。如果没有看过上一篇的话,建议先看一下 CLH锁简介,本篇是在上一篇的基础上做了简单的描述。
简述
MCS 的名字也是由发明人的名字字幕组合来的(John Mellor-Crummey and Michael Scott)。MCS与CLH最大的不同是,CLH在前驱节点上判断是否获得锁,MCS则是在自身的节点上判断是否能够获取到锁。
Java代码
public class MCSLock {
public class QNode {
volatile boolean locked = false;
QNode next = null;
}
private AtomicReference<QNode> tail;
private ThreadLocal<QNode> myNode;
public MCSLock() {
tail = new AtomicReference<QNode>(null);
myNode = new ThreadLocal<QNode>() {
protected QNode initialValue() {
return new QNode();
}
};
}
public void lock() {
QNode qnode = myNode.get();
QNode pred = tail.getAndSet(qnode);
if (null != pred) {
qnode.locked = true;
pred.next = qnode;
}
while (qnode.locked) {
}
}
public void unlock() {
QNode qnode = myNode.get();
if (null == qnode.next) {
if (tail.compareAndSet(qnode, null)) {
return;
}
while (null == qnode.next) {
}
}
qnode.next.locked = false;
qnode.next = null;
}
}
窥探原理
MCS同样有一个tail节点,也是存放最后一次申请锁的对象,另外它只有一个myNode属性。它的QNode对象内部除了维护一个boolean类型的locked变量,还有一个QNode类型的next变量,用来指向下一个节点。CLH锁等待队列是一个逻辑上的队列,而MCS锁的等待队列是一个显示的单向链表。
lock()
lock方法先将自身的locked属性设置为true,将tail节点上的对象的next变量设置为自己,然后在自身的locked变量上等待锁。
unlock()
unlock方法先判断后面有没有对象在等待获取锁,如果有的话将后面对象的locked变量设置为false,即通知后面的对象可以执行任务了,随即将自身的next至空(因为是ThreadLocal类型的变量,用完清理,在上一篇讲解CLH锁的时候有说明),如果后面没有对象在等待的话,将tail节点至空(这里是一个CAS操作),如果CAS至空操作失败,证明后面又有对象申明了锁,下面的 while(null == qnode.next) 循环则是为了确保后面的对象被成功加入到等待队列(链表)的保护工作。