CLH的核心是:当前节点自旋前一个节点的状态locked状态。当前一个节点locked状态是false时,当前节点可以从自旋状态退出执行后续方法。自旋的节点无法执行后续逻辑,代表未获得锁。退出自旋的节点可以执行后续逻辑表示已获取锁。
每次需要尝试获取锁的节点locked状态都设置true(解释一下当前节点locked的状态为什么要设置成true,locked状态是给后继节点自旋检测用的,当前节点都在尝试获取锁,后继节点一定不能获得锁,需要进行自旋;通俗的讲就是狗大哥都没吃饱,其他狗小弟更不能吃)。
并且将该节点设置成末尾节点,然后返回之前的末尾节点,返回后当前节点检测返回的末尾节点的locked状态,如果状态是false则不需要自旋,如果是true则需要自旋。依此类推......
假如我现在依次添加的节点A,B,C,D。那么最开始A是设置成末尾节点,并返回之前tail节点(初始化了一个tail节点,可以看下面的源码),A检测tail节点locked状态。
然后是B是末尾节点,B的前一个末尾节点是A,那么B检测A的locked状态。
C检测B的locked状态。D检测Alocked状态。
node1就是A,node2就是B,node3就是C,node4没写(真写不下了)。
package ltd.wzrj.lock;
import java.util.concurrent.atomic.AtomicReference;
public class CLHLock {
// node每个线程都有一个副本
private static final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);
private static final AtomicReference<Node> tail = new AtomicReference<>(new Node());
static final class Node {
private volatile boolean locked;
Node() {
this.locked = false;
}
}
/**
* 尝试获得锁
*/
public void tryLock() {
// 从当前线程ThreadLocal获取当前节点
Node node1 = this.node.get();
// 设置当前节点为站锁
node1.locked = true;
// 设置尾部节点 并返回之前的尾节点
Node pre =this.tail.getAndSet(node1);
// 当前节点判断之前的尾部节点状态,如果是false则获得锁成功,从自旋中醒来
while(pre.locked){
System.out.println(Thread.currentThread().getName()+"等待中...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 释放锁
*/
public void unlock() {
// 从当前线程ThreadLocal获取当前节点
Node Node = this.node.get();
// 设置当前节点的状态是为解锁状态,后面自旋的线程就能检测
Node.locked = false;
/* 重置该节点,避免该节点再次获得锁后,造成死锁。
(解释一下为什么能该节点还能获得锁,并造成死锁。因为在lock方法中,判断的是当前节点的前一个节点状态。
当前节点能获得锁后,释放锁说明当前节点的前一个节点是状态是false,
那么当当前节点再次执行lock方法时也可以执行lock方法,所以执行lock方法后,会把这个节点添加到尾节点之后,状态改成true
【如果添加到尾节点,检测尾节点的状态,进入自旋,那么当前节点的后面的一个节点(命名B)就有两种情况:
1、B获得锁。当我当前线程释放锁之后,B在当前节点进入lock方法之前马上获得锁,程序正常运行。
2、B没有获得锁。当当前线程释放锁之后,B节点没有获得锁,那么B节点还在等我当前节点状态改成false,
但是我当前节点已经执行了lock方法,locked变成了true,进入自旋,
那么在B的后续节点中就没有locked为false的节点了,造成了所有节点一直自旋,无解。】)
如果我新的当前节点,那么即使重新进入了lock方法,新的节点会到尾部节点,而旧的节点不变,B还是指向旧的节点
*/
this.node.set(new Node());
}
}
public class CLHLockTest {
private final CLHLock clhLock = new CLHLock();
private void testLock() {
// 加锁
clhLock.lock();
try {
System.out.println(Thread.currentThread().getName() +"获得锁" );
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 解锁
clhLock.unlock();
}
}
public static void main(String[] args) {
CLHLockTest instance = new CLHLockTest();
// 启动100线程做自增操作
IntStream.range(0, 2).forEach(e -> new Thread(instance::testLock).start());
}
}