
在并发编程中,锁是一种常用的保证线程安全的方法。Java中常用的锁主要有两类,一种是Synchronized修饰的锁,被称为Java内置锁或监视器锁。另一种就是J2SE 1.5版本之后的java.util.concurrent包中的各类同步器,包括ReentrantLock(可重入锁),ReentrantReadWriteLock(可重入读写锁),Semaphore(信号量),CountDownLatch等。这些同步器都是基于AbstractQueueSynchronizer这个简单的框架来构建的,而AQS的核心数据结构是一种名叫Craig,Landin,and Hagersten locks(下称CLH锁)的变体。
CLH锁是对自旋锁的一种改良。在介绍CLH锁前,我先简单介绍一下自旋锁。
自旋锁是互斥锁的一种实现,Java实现如下方所示。
public class SpinLock {
private AtomicReference owner = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
while (!owner.compareAndSet(null, thread)) {
}
}
public void unlock() {
Thread thread = Thread.currentThread();
while (!owner.compareAndSet(thread, null)) {
}
}
}
如代码所示,获取锁时,线程会对一个原子变量循环执行compareAndSet方法,直到该方法返回成功时即为成功获取锁。compareAndSet方法底层是通用的compare-and-swap(下称CAS)实现的。该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。该操作原子的。该操作时原子操作,原子性保证了根据最新信息计算新值,如果于此同时值已由另一个线程更新,则写入将失败。因此,这段代码可以实现互斥锁的功能。
1.2自旋锁缺点自旋锁实现简单,同时避免了操作系统进程调度和线程上下文切换的开销,但它有两个缺点:
自旋锁适用于锁竞争不激烈、锁持有时间短的场景。
2 CLH锁 2.1 什么是CLH锁CLH锁是对自旋锁的一种改进,有效的解决了以上的两个缺点。首先它将线程组织成一个队列,保证先请求的线程先获得锁,避免了饥饿问题。其次锁状态去中心化,让每个线程在不同的状态变量中自旋,这样当一个线程释放它的锁时,只能使其直接后续线程的高速缓存失效,缩小了影响范围,从而减少了CPU的开销。
CLH锁结构很简单,类似一个链表队列,所有请求获取锁的线程会排队在链表队列中,自旋访问队列中前一个节点的状态。当一个节点释放锁时,只有它的后一个节点才可以得到锁。CLH锁本身有一个队尾指针Tail,它是一个原子变量,指向队列最末端的CLH节点。每个CLH节点有两个属性:所代表的线程和标识是否持有锁的状态变量。当一个线程要获取锁时,它会对Tail进行一个getAndSet的原子操作。该操作会返回Tail当前指向的节点,也就是当前队尾节点,然后使Tail指向这个线程对应的CLH节点,成为新的队尾节点。入队成功后,该线程会轮询上一个队尾节点的状态变量,当上一个节点释放锁后,它将得到这个锁。
下面用图来展示CLH锁从获取到释放锁的全过程。
通过上面的图形象的展示了CLH的数据结构以及初始化、获取 、释放锁的全过程,编译大家理解CLH锁的原理。但是就是理解了原理,也不一定能够实现一个线程安全的CLH互斥锁。在并发编程领域,“细节是魔鬼”这一格言同样适用。下面讲解读CLH锁Java实现源码并分享并发编程的一些细节。
public class CLH {
private final ThreadLocal node = ThreadLocal.withInitial(Node::new);
private final AtomicReference tail = new AtomicReference<>(new Node());
private static class Node {
private volatile boolean locked;
}
public void lock() {
Node node = this.node.get();
node.locked = true;
Node pre = tail.getAndSet(node);
while (pre.locked) {
}
}
public void unLock() {
Node node = this.node.get();
node.locked = false;
this.node.set(new Node());
}
}
CLH锁作为自旋锁的改进,有以下几个优点:
当然,它也有两个缺点:
Java AQS 核心数据结构 -CLH 锁
上一篇 The POM for <name> is invalid, transitive dependencies (if any) will not be available
下一篇 vite build、Flask运行后报错Failed to load module script. Strict MIME type checking is enforced