最近在看有关java面试的相关材料,看到自旋锁的CHLLock部分。
看的似乎有些心得,但不确定是不是准确无误,先记录下此时的想法,待以后不断地学习深入后再回看这篇文章是否有疏漏或者出现一些谬误;当然也非常欢迎大家来对本文做批评指正。
这里引用SnailClimb在CSDN博客中的有关源码,并增加了一些我自己理解的一些注释。
CHLLock
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* CLH的发明人是:Craig,Landin and Hagersten。
* 代码来源:http://ifeve.com/java_lock_see2/
*/
public class CLHLock {
/**
* 定义一个节点,默认的lock状态为true
*/
public static class CLHNode {
private volatile boolean isLocked = true;
}
/**
* 尾部节点,只用一个节点即可
*/
private volatile CLHNode tail;
private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, CLHNode.class,
"tail");
public void lock() {
// 新建节点并将节点与当前线程保存起来
CLHNode node = new CLHNode();
LOCAL.set(node);
// 将新建的节点设置为尾部节点,并返回旧的节点(原子操作),这里旧的节点实际上就是当前节点的前驱节点
//个人理解=>大概相当于把AtomicReferenceFieldUpdater中原有的tail取出,并用新建的节点将原有的tail替代,这个操作是原子性的。
//操作原子性的由来:AtomicReferenceFieldUpdater是一个基于反射的工具类,它能对指定类的指定的**volatile**引用字段进行原子更新。(注意这个字段不能是private的)见引用3。
CLHNode preNode = UPDATER.getAndSet(this, node);
if (preNode != null) {
// 前驱节点不为null表示当锁被其他线程占用,通过不断轮询判断前驱节点的锁标志位等待前驱节点释放锁
while (preNode.isLocked) {
}
preNode = null;
LOCAL.set(node);
}
// 如果不存在前驱节点,表示该锁没有被其他线程占用,则当前线程获得锁
}
public void unlock() {
// 获取当前线程对应的节点
//对应博客中的这句话:申请线程只在本地变量上自旋,避免了多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次
//读写操作都必须在多个处理器缓存之间进行缓存同步
CLHNode node = LOCAL.get();
// 如果tail节点等于node,则将tail节点更新为null,同时将node的lock状态职位false,表示当前线程释放了锁
if (!UPDATER.compareAndSet(this, node, null)) {
node.isLocked = false;
}
node = null;
}
}
这个方法主要:繁重的系统总线和内存的流量,大大降低系统整体的性能。(来自引用2)
public void lock() {
// 新建节点并将节点与当前线程保存起来
CLHNode node = new CLHNode();
LOCAL.set(node);
// 将新建的节点设置为尾部节点,并返回旧的节点(原子操作),这里旧的节点实际上就是当前节点的前驱节点
CLHNode preNode = UPDATER.getAndSet(this, node);
实现公平性的代码主要是在上述代码中,由于AtomicReferenceFieldUpdater是针对volatile的。因此,对其的操作是所有线程可见的(在注解4中,了解到此方法不适用于在NUMA 架构下使用,只能说记下了,但不太了解是为啥)线程们按时间顺序对AtomicReferenceFieldUpdater中的tail进行替换,按时间顺序持有上一个替换了tail的Node,并对其进行监听,这样就能实现先请求的线程能先获得锁。
引用来源
本文主要参考来源:java面试材料github地址1、面试必备之深入自旋锁2、有关AtomicReferenceFieldUpdater3、并发编程网4