CLH锁
所谓CLH锁是并行计算中自旋锁的实现机制之一。
自旋的含义是如果一个Atomic寄存器的值不符合要求,就一直进行循环。这与阻塞的区别是:阻塞会产生现场恢复的开销,而自旋锁省却了这一部分,同时自旋锁需要一直占用CPU。
它隶属于队列锁,它是对ALock的一种改进。
原始代码
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
class QNode{
boolean locked;
}
public class CLHLock implements java.util.concurrent.locks.Lock {
AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());
ThreadLocal<QNode> myPred;
ThreadLocal<QNode> myNode;
public CLHLock() {
tail = new AtomicReference<QNode>(new QNode());
myNode = new ThreadLocal<QNode>() {
protected QNode initialValue() {
return new QNode();
}
};
myPred = new ThreadLocal<QNode>() {
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());
}
}
解析
这段代码我们初看可能看不懂。不要着急,让浩哥给你讲个故事吧。
在代码世界中有一个笑傲江湖的平行宇宙。在这个平行宇宙中有一个武林盟主——东方不败,他可以号令群雄,但是由于一些众所周知的原因,他整日无所事事,沉湎于酒肉男色,这就激起了武林中另一群野心阴谋家的注意。这群野心家有个共同的名字阴谋家,他们希望拿到那个号令武林的令牌来兴风作浪,实现自己的一些小阴谋。
有一天岳不群伪造了一颗令牌并在其上安装了一个小芯片以便自己以后寻找。他找到东方不败,用自己的假令牌移花接木换到了真令牌,开始了为非作歹的生涯。这个过程就是上锁。
在之后的某一天,岳不群想要退隐江湖,于是他通过gps找到了自己原来的那颗令牌。找到令牌的一刻他惊呆了,令牌居然不在东方不败手中,居然在林平之手中。事情的经过是这样的,林平之也想号令武林,因为阴谋家的阴谋总是类似的,他也找到东方不败换了令牌,但是他万万没有想到,换到的令牌居然也是假的,他大失所望,直到岳不群自投罗网,他抢走了岳不群的令牌,开启了自己为非作歹的时代。这个过程就是第一个线程解锁,并将锁转移到等待者的过程。
历史的发展大浪淘沙,无数阴谋家出现又退隐,有一天,直到最后一个阴谋家退隐,他将锁还给东方不败,这个武林又恢复了以往的平静。同时也在等待着下一轮的潮起潮落。
代码重构
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public abstract class CLHLock implements Lock{
//every yuebuqun knows the real king-dongfangbubai
AtomicReference<Qnode> dongfangbubai;
//I have a token which is a fake one when initialized.
ThreadLocal<Qnode> myToken = new ThreadLocal<Qnode>(){
protected Qnode initialValue(){
return new Qnode();
}
};
//It's a gps so I can find my token with it.
ThreadLocal<Qnode> gps = new ThreadLocal<Qnode>(){
protected Qnode initialValue(){
return null;
}
};
public CLHLock(){
Qnode qnode = new Qnode();
qnode.locked.set(false);
this.dongfangbubai = new AtomicReference<Qnode>(qnode);
}
@Override
public void lock() {
//link my token with my gps.
Qnode qnode = myToken.get();
gps.set(qnode);
//swap my bad node with the dongfangbubai(king)
myToken.set(dongfangbubai.getAndSet(qnode));
//spin until unlock
while(myToken.get().locked.get()){;}
}
@Override
public void unlock() {
//find my token
//and swap the token with mine.
gps.get().locked.set(false);
myToken.get().locked.set(true);
}
}
class Qnode{
AtomicBoolean locked = new AtomicBoolean(true);
}