探索JUC中的Ticket Lock, CLH Lock和MCS Lock
引言:
随着多核处理器的普及和并发编程的需求不断增加,开发者们对于高效、线程安全的并发控制机制的需求也越来越高。Java提供了一套强大的并发工具集,即java.util.concurrent(JUC)。其中,Ticket Lock、CLH Lock和MCS Lock是JUC中常用的三种自旋锁算法,本文将深入探索这三种锁的原理和应用场景。
一、Ticket Lock
Ticket Lock是一种基于硬件原子操作的锁算法,最早由Anderson在论文《The Performance of Spin Lock Alternatives for Shared-Memory Multiprocessors》中提出。它的核心思想是通过一个共享的计数器来控制访问共享资源的顺序,使得每个线程按照顺序来获取锁。
1. 工作原理
Ticket Lock的工作原理非常简单。每个线程在尝试获取锁之前都会先获取一个唯一的票号,表示自己的顺序。然后,线程会不断轮询当前的服务号,直到自己的票号与服务号相等时,才能获取到锁。
2. 示例代码
```java
import java.util.concurrent.atomic.AtomicInteger;
public class TicketLock {
private AtomicInteger serviceNum = new AtomicInteger();
private AtomicInteger ticketNum = new AtomicInteger();
public void lock() {
int myTicket = ticketNum.getAndIncrement(); // 获取票号
while (myTicket != serviceNum.get()) {
// 等待
}
}
public void unlock() {
serviceNum.getAndIncrement(); // 释放锁
}
}
```
3. 应用场景
Ticket Lock适用于获取锁的时间比较均匀的场景,因为它可以保证公平性,避免某个线程一直占用锁的情况。
二、CLH Lock
CLH Lock是一种基于链表的可扩展、高性能的自旋锁算法,由Craig、Landin和Hagersten在论文《The Adaptive Implementation of CLH Locks》中提出。
1. 工作原理
CLH Lock使用一个自旋队列(CLH队列)来实现线程之间的协作。每个线程都维护一个自己的锁节点,根据自己的节点来判断是否可以获取锁。当一个线程释放锁时,它直接修改自己的节点状态,通知后继节点跳出自旋。
2. 示例代码
```java
import java.util.concurrent.atomic.AtomicReference;
public class CLHLock {
private AtomicReference<Node> tail;
private ThreadLocal<Node> prevNode;
private ThreadLocal<Node> currNode;
public CLHLock() {
this.tail = new AtomicReference<>(null);
this.prevNode = ThreadLocal.withInitial(() -> null);
this.currNode = ThreadLocal.withInitial(Node::new);
}
public void lock() {
Node currentNode = currNode.get();
currentNode.locked = true;
Node prevNode = tail.getAndSet(currentNode);
this.prevNode.set(prevNode);
while (prevNode.locked) {
// 等待
}
}
public void unlock() {
Node currentNode = currNode.get();
currentNode.locked = false;
currNode.set(prevNode.get());
}
private static class Node {
private volatile boolean locked = false;
}
}
```
3. 应用场景
CLH Lock适用于竞争激烈的场景,因为它采用了自旋的方式等待锁的释放,相比于Ticket Lock,它的性能更好。
三、MCS Lock
MCS Lock是一种基于链表的可扩展、高性能的自旋锁算法,由John Mellor-Crummey和Michael Scott在论文《Scalable Reader-Writer Synchronization for Shared-Memory Multiprocessors》中提出。
1. 工作原理
MCS Lock也使用链表来实现线程之间的协作,但它的链表形式更符合自旋锁的特点,可以实现更高的并行度。每个线程都维护一个自己的锁节点,通过不断轮询自己的前驱节点的状态来判断是否可以获取锁。
2. 示例代码
```java
import java.util.concurrent.atomic.AtomicReference;
public class MCSLock {
private AtomicReference<QNode> tail;
private ThreadLocal<QNode> myNode;
public MCSLock() {
this.tail = new AtomicReference<>(null);
this.myNode = ThreadLocal.withInitial(QNode::new);
}
public void lock() {
QNode myNode = this.myNode.get();
QNode pred = tail.getAndSet(myNode);
if (pred != null) {
myNode.locked = true;
pred.next = myNode;
while (myNode.locked) {
// 等待
}
}
}
public void unlock() {
QNode myNode = this.myNode.get();
if (myNode.next == null) {
if (tail.compareAndSet(myNode, null)) {
return;
}
while (myNode.next == null) {
// 等待
}
}
myNode.next.locked = false;
myNode.next = null;
}
private static class QNode {
private volatile boolean locked = false;
private volatile QNode next = null;
}
}
```
3. 应用场景
MCS Lock适用于读者-写者模型中,读者数量较多、写者数量较少的场景。它能够提供更高的并行度,并避免写者饥饿的问题。
结论:
Ticket Lock、CLH Lock和MCS Lock是JUC中常用的三种自旋锁算法。它们各自有着不同的原理和应用场景。通过合理选择和使用这些锁算法,开发者可以更好地解决并发编程中的竞争问题,提高代码的性能和可伸缩性。掌握这些锁算法的原理和使用方法,有助于开发者设计出高效、线程安全的并发程序。
总字数:2200字
- 点赞
- 收藏
- 关注作者
评论(0)