CodeArena

ReentrantLock 和 公平锁

2023-10-23
最后更新:2024-05-23
4分钟
741字

ReentrantLock 和 公平锁

ReentrantLock 是基于 Lock 实现的可重入锁,所有的 Lock 都是基于 AQS 实现的。

AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。

它的可重入是因为实现了同步器 Sync,这个抽象类 Sync 继承了AQS,在 Sync 的两个实现类中,包括了公平锁和非公平锁。

img.png

同步器 Sync 继承自 AbstractQueuedSynchronizer 抽象队列同步器

ReentrantLock默认是非公平锁,在构造函数中传入true 构建公平锁

1
public ReentrantLock() {
2
sync = new NonfairSync();
3
}
4
5
public ReentrantLock(boolean fair) {
6
sync = fair ? new FairSync() : new NonfairSync();
7
}

一般情况下并不需要公平锁,除非你的场景中需要保证顺序性

CLH 基于单向链表的公平锁

CLH.png

1
public class CLHLock implements Lock {
2
3
private final ThreadLocal<CLHLock.Node> prev;
4
private final ThreadLocal<CLHLock.Node> node;
5
private final AtomicReference<CLHLock.Node> tail = new AtomicReference<>(new CLHLock.Node());
6
7
public CLHLock() {
8
this.prev = ThreadLocal.withInitial(() -> null);
9
this.node = ThreadLocal.withInitial(CLHLock.Node::new);
10
}
11
12
13
private static class Node {
14
private volatile boolean locked;
15
}
22 collapsed lines
16
17
@Override
18
public void lock() {
19
final Node node = this.node.get();
20
node.locked = true;
21
//把新加入的结点设置成尾部结点
22
//getAndSet 获取当前值并设置新值
23
Node predNode = this.tail.getAndSet(node);
24
this.prev.set(predNode);
25
//自旋
26
while (predNode.locked) ;
27
}
28
29
@Override
30
public void unlock() {
31
final Node node = this.node.get();
32
node.locked = false;
33
this.node.set(this.prev.get());
34
}
35
36
...
37
}

MCS

和CLH一样也是一种基于链表的可扩展、高性能、公平的自旋锁

不同点:它是真的有下一个节点 next,添加这个真实节点后,它就可以只在本地变量上自旋,而 CLH 是前驱节点的属性上自旋。

CLH 更需要线程数据在同一块内存上效果才更好— SMP 架构

MCS 更适合 NUMA 非一致存储访问架构,无论数据是否分散在不同的CPU模块都没有影响

1
public class MCSLock implements Lock {
2
private AtomicReference<MCSLock.Node> tail = new AtomicReference<>(null);
3
4
private ThreadLocal<MCSLock.Node> node;
5
6
private static class Node {
7
private volatile boolean locked = false;
8
private volatile Node next = null;
9
}
10
11
public MCSLock() {
12
this.node = ThreadLocal.withInitial(Node::new);
13
}
14
15
@Override
28 collapsed lines
16
public void lock() {
17
Node node = this.node.get();
18
Node preNode = tail.getAndSet(node);
19
if (null == preNode) {
20
node.locked = true;
21
return;
22
}
23
node.locked = false;
24
preNode.next = node;
25
while (!node.locked) ;
26
}
27
28
@Override
29
public void unlock() {
30
Node node = this.node.get();
31
if (null != node.next) {
32
node.next.locked = true;
33
node.next = null;
34
return;
35
}
36
if (tail.compareAndSet(node, null)) {
37
return;
38
}
39
while (node.next == null) ;
40
}
41
42
...
43
}

TicketLock

TicketLock 就像你去银行、呷哺给你的一个排号卡一样,叫到你号你才能进去。属于严格的公平性实现,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量,每次读写操作都需要进行多处理间的缓存同步,非常消耗系统性能。

代码实现上也比较简单,lock() 中设置拥有者的号牌,并进入自旋比对。unlock() 中使用 CAS 进行解锁操作,并处理移除。

1
public class TicketLock implements Lock {
2
3
private AtomicInteger serviceCount = new AtomicInteger(0);
4
private AtomicInteger ticketCount = new AtomicInteger(0);
5
private final ThreadLocal<Integer> owner = new ThreadLocal<>();
6
7
@Override
8
public void lock() {
9
owner.set(ticketCount.getAndIncrement());
10
while (serviceCount.get() != owner.get());
11
}
12
13
@Override
14
public void unlock() {
15
serviceCount.compareAndSet(owner.get(), owner.get() + 1);
5 collapsed lines
16
owner.remove();
17
}
18
19
...
20
}
本文标题:ReentrantLock 和 公平锁
文章作者:Echoidf
发布时间:2023-10-23
感谢大佬送来的咖啡☕
alipayQRCode
wechatQRCode