从自旋锁 到 CLH 到 AQS

这篇主要讲讲, 为啥 会有 CLH,以及为啥会有 AQS,他们主要做了啥。

CAS 

首先说下 CAS,之后的锁都以此为基础, cpu保证了他的 原子性

自旋锁

多个线程忙等待锁释放,由于不牵涉到信号量,不依赖操作系统的调度,在同一个上下文内,所以获取和释放锁的开销很小。

但是等待锁期间会占用大量cpu。

简单例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SpinLock {
private AtomicReference<Thread> cas = new AtomicReference<Thread>();
public void lock() {
Thread current = Thread.currentThread();
// 利用CAS
while (!cas.compareAndSet(null, current)) {
// DO nothing
}
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
}
}

公平自旋锁

但是自旋锁有个问题,当有多个自旋锁存在时,是抢占式的,无法控制优先级,会造成线程饥饿

为了解决公平性问题,于是就想出了维护一个自旋锁链,实现上有几个代表

TicketLock, CLH, MCS

TicketLock

每一个想要获取锁的线程都分配一个 自增的 ticket,各个自旋锁自旋公共的ticket

缺点,多线程并发访问公共字段,为了保证可见性,每次读写操作都必须在多个处理器缓存之间进行缓存同步, 会降低系统整体性能

CLH

将锁维护在一个链表上,每个锁自旋本线程的本地变量(该变量为前驱状态),当自己的前驱unlock后,将会修改后置链表节点的前驱状态,以便后面线程获取到锁。

线程切换仅发生在 unlock 的操作时

MCS

同 CLH 锁, 自旋本地变量,本地变量的状态再前置线程unlock时修改。

线程切换仅发生在 unlock 的操作时

总结:

CLH 的实现比 MCS 更简单。

CLH 和 MCS 的代码可以参考这里

https://coderbee.net/index.php/concurrent/20131115/577

AQS

AQS是基于CLH 修改的.

公平性靠CLH链实现,但是将自旋锁 替换成了 同步阻塞器

reference

https://coderbee.net/index.php/concurrent/20131115/577

avatar

lelouchcr's blog