最近在看关于线程锁的算法,做一个整理。资料出自于《The Art of Multiprocessor Programming》,算是一个读书笔记吧。示范代码基于java。
第一章是Craig, Landin, and Hagersten (CLH) locks。先上CLHLock的实现逻辑:
public class CLHLock implements Lock {
private final AtomicReference tail;
private final ThreadLocal myPred;
private final ThreadLocal myNode;
public CLHLock() {
tail = new AtomicReference(new QNode());
myNode = new ThreadLocal() {
protected QNode initialValue() {
return new QNode();
}
};
myPred = new ThreadLocal();
}
@Override
public void lock() {
QNode node = myNode.get();
node.locked = true;
QNode pred = tail.getAndSet(node);
myPred.set(pred);
while (pred.locked) {
}
}
@Override
public void unlock() {
QNode node = myNode.get();
node.locked = false;
myNode.set(myPred.get());
}
private static class QNode {
volatile boolean locked;
}
}
CLH Lock是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。
锁本身被表示成QNode的虚拟链表。共享的tail保存申请的最后的一个申请lock的线程的myNode域。每个申请lock的线程通过一个本地线程变量pred指向它的前驱。另外一个本地线程变量myNode的locked保存本身的状态,true:正在申请锁或已申请到锁;false:已释放锁。每个线程通过监视自身的前驱状态,自旋等待锁被释放。释放锁时,把自身myNode的locked设为false,使后继线程获的锁,并回收前驱的QNode,等待下次使用,因为自身的QNode可能被tail获后继引用,而前驱不再使用老的QNode。
该算法也是空间有效的,如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个myNode,L个锁有L个tail。这种算法有一个缺点是在NUMA系统架构下性能表现很差,因为它自旋的locked是前驱线程的,如果内存位置较远,那么性能会受到损失。但是在SMP这种cache一致性的系统架构上表现良好。
下一章会整理MCSLock,它更适用于NUMA架构。
分享到:
相关推荐
自旋锁是多线程编程中的一个重要概念,用于在共享资源上实现轻量级的同步。...- 锁的释放过程:如何通知等待线程锁已可用。 请务必仔细研究这些代码,它们提供了实践自旋锁公平性策略的宝贵资料。
1. **可重入性**:ReentrantLock允许一个线程多次获取同一锁,即在持有锁的情况下可以再次获取,这与synchronized的可重入性相同。 2. **公平性与非公平性**:ReentrantLock可以通过构造函数参数选择是否为公平锁。...
- 可以通过tryLock尝试获取锁,增加灵活性。 - 提供更精细的锁释放控制,避免异常导致的死锁。 #### 5. 认识AQS - **AQS内部实现**: - 通过`state`字段记录锁的状态,使用CAS操作进行原子更新。 - 通过FIFO...
- **获取锁**:线程尝试获取锁,首先检查state是否为0,如果是则成功获取(无锁状态),并更新state为1,表示锁被占用。否则,线程将变为等待状态,加入到等待队列。 - **入队**:新到来的线程在等待队列尾部插入...
2. `ReentrantLock` 的 `lock()` 方法:加锁的基本过程包括三个步骤:(1)线程尝试竞争锁;(2)竞争锁失败,线程封装成节点加入CLH双向链表阻塞队列;(3)线程进行自旋,尝试竞争锁,竞争锁失败,阻塞排队的线程...
ConcurrentHashMap的高效实现还依赖于许多并发控制技术,比如无锁数据结构(Lock-Free)、自旋锁(Spin Lock)、循环等待检测(CLH Lock)以及Compare and Swap (CAS)操作。这些技术使得ConcurrentHashMap在不牺牲太...
当线程调用ReentrantLock的`lock()`方法时,实际是调用了`FairSync`类中的`lock()`方法,这个方法最终会调用`acquire(1)`。这里的数字1表示设置锁的状态,对于独占锁来说,初始状态为0,每次获取锁会使状态加1,...
如果不能获取,则将线程加入等待队列,并通过内部的`CLHLock`算法实现线程的排队等待。 ##### 共享模式 共享模式与独占模式类似,但允许多个线程同时获取同步状态。在共享模式下,AQS通过`tryAcquireShared`方法...
相比于多线程防止CPU空转Quine 不通过读源文件的方式输出程序源代码本身Vertx 基于netty的NIO实现的小清新WebServer和HttpClientReentrantReadWriteLock的用例以及实现写锁降级为读锁实现三种自旋锁SpinLock直接基于...
3. CLHLock(Craig, Landin, and Hagersten Lock)和MCSLock(Mellor-Crummey and Scott Lock):这两种自旋锁是基于链表结构的公平锁,它们将等待线程组织成一个队列,并使用`AtomicReferenceFieldUpdater`等原子...
例如,在`ReentrantLock`的实现中,`lock()`方法的非公平版本会尝试直接获取锁,如果失败则会进入队列等待。 对于锁的升级过程,Java的`synchronized`关键字在低并发情况下使用的是偏向锁,当只有一个线程访问同步...
- **排队机制**:AQS内部使用CLH锁队列(Craig-Landin-Hagersten lock queue)作为线程等待队列的基础结构。每个等待同步状态改变的线程会被封装成一个节点(`Node`),并加入到队列中。当线程获得同步状态后,会从...
13. **ReentrantLock的实现**:基于AQS(AbstractQueuedSynchronizer),使用CLH锁(自旋锁)维护一个双向队列,线程通过不断轮询前一个节点状态等待锁的释放。 14. **RDB快照**:Redis使用bgSAVE命令创建子进程...