多线程与高并发 —JUC 之 AQS、ReentrantLock、读写锁原理等

转载自:
https://zhangc233.github.io/2021/06/12/多线程与高并发—AQS、ReentrantLock、读写锁原理/

AQS

概述

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。

特点

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁:

    • getState:获取 state 状态。
    • setState:设置 state 状态。
    • compareAndSetState:cas 机制设置 state 状态。
    • 独占模式只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源。
  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList。

  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet。

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException):

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively
1
2
3
4
5
6
7
8
9
10
11
// 获取锁的姿势
// 如果获取锁失败
if (!tryAcquire(arg)) {
// 入队, 可以选择阻塞当前线程 park unpark
}

// 释放锁的姿势
// 如果释放锁成功
if (tryRelease(arg)) {
// 让阻塞线程恢复运行
}

自定义同步器

下面实现一个不可重入的阻塞式锁:使用 AbstractQueuedSynchronizer 自定义一个同步器来实现自定义锁,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class UnRepeatLock {
public static void main(String[] args) {
MyLock myLock = new MyLock();
new Thread(() -> {
myLock.lock();
log.info("lock ... ");
// 测试是否不可重入
myLock.lock();
try {
log.info("starting...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.info("unlock ... ");
myLock.unlock();
}
},"t1").start();
}
}

class MyLock implements Lock {
class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}

@Override
protected boolean tryRelease(int arg) {
if (compareAndSetState(1, 0)) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
return false;
}

@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}

public Condition newCondition() {
return new ConditionObject();
}
}

private MySync mySync = new MySync();

//加锁
@Override
public void lock() {
mySync.acquire(1);
}

// 可中断的锁
@Override
public void lockInterruptibly() throws InterruptedException {
mySync.acquireInterruptibly(1);
}

// 只会尝试一次加锁
@Override
public boolean tryLock() {
return mySync.tryAcquire(1);
}

// 带超时时间的
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return mySync.tryAcquireNanos(1,unit.toNanos(time));
}

// 解锁
@Override
public void unlock() {
mySync.release(1);
}

// 创建条件变量
@Override
public Condition newCondition() {
return mySync.newCondition();
}
}

输出:

1
2
19:35:34.374 [t1] INFO com.heu.test.UnRepeatLock - lock ... 
一直等待。。。

AQS 目标

AQS 要实现的功能目标

  • 阻塞版本获取锁 acquire 和非阻塞的版本尝试获取锁 tryAcquire

  • 获取锁超时机制

  • 通过打断取消机制

  • 独占机制及共享机制

  • 条件不满足时的等待机制

要实现的性能目标

  • Instead, the primary performance goal here is scalability: to predictably maintain effiffifficiency even, orespecially, when synchronizers are contended.

AQS设计

获取锁的逻辑:

1
2
3
4
5
6
while(state 状态不允许获取) {
if(队列中还没有此线程) {
入队并阻塞
}
}
当前线程出队

释放锁的逻辑:

1
2
3
if(state 状态允许了) {
恢复阻塞的线程(s)
}

要点:

  • 原子维护 state 状态

  • 阻塞及恢复线程

  • 维护队列

state 设计

  • state 使用 volatile 配合 cas 保证其修改时的原子性。
  • state 使用了 32bit int 来维护同步状态,因为当时使用 long 在很多平台下测试的结果并不理想。

阻塞恢复设计

  • 早期的控制线程暂停和恢复的 api 有 suspend 和 resume,但它们是不可用的,因为如果先调用的 resume ,那么 suspend 将感知不到。

  • 解决方法是使用 park & unpark 来实现线程的暂停和恢复,具体原理在之前讲过了,先 unpark 再 park 也没问题。

  • park & unpark 是针对线程的,而不是针对同步器的,因此控制粒度更为精细。

  • park 线程还可以通过 interrupt 打断。

**** 队列设计

  • 使用了 FIFO 先入先出队列,并不支持优先级队列。

  • 设计时借鉴了 CLH 队列,它是一种单向无锁队列。

image-20210616201035669 image-20210616201054697

队列中有 head 和 tail 两个指针节点,都用 volatile 修饰配合 cas 使用,每个节点有 state 维护节点状态。

主要用到 AQS的并发工具类

image-20210616202126371

ReentrantLock 原理

可以看到 ReentrantLock 提供了两个同步器,实现公平锁和非公平锁,默认是非公平锁!

image-20210616194522522

非公平锁实现原理

加锁流程

先从构造器开始看,默认为非公平锁实现。

1
2
3
public ReentrantLock() {
sync = new NonfairSync();
}

NonfairSync 继承自 AQS,在没有线程竞争时:

1
2
3
4
5
6
7
8
9
final void lock() {
// 没有竞争时, 直接加锁 (CAS)
if (compareAndSetState(0, 1))
// 设置持有锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 有竞争, 会调用这个方法
acquire(1);
}

image-20210616195628434

第一个竞争出现时:

1
2
3
4
5
6
public final void acquire(int arg) {
// 再次尝试加锁失败,则创建一个 Node 节点对象加入到等待队列中去.
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

image-20210616200052450

Thread-1 执行了:

  1. lock方法中CAS,尝试将 state 由 0 改为 1,结果失败;
  2. lock方法中进一步调用acquire方法,进入 tryAcquire 逻辑,这里认为此时 state 已经是1,结果仍然失败;
  3. 接下来进入 acquire方法的addWaiter 逻辑,构造 Node 队列:
    • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态;
    • Node 的创建是懒惰的;
    • 其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程。

image-20210616202800247

之后当前线程进入acquire方法的 acquireQueued 逻辑:

  1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞;
  2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,这里设置此时 state 仍为 1,失败;
  3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false;

image-20210616203601530

  1. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败;
  2. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回 true;
  3. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示已经阻塞)。

image-20210616204249004

如果再次有多个线程经历上述过程竞争失败,变成这个样子:

image-20210616204842320

加锁源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Sync 继承自 AQS
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;

// 加锁实现
final void lock() {
// 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 如果尝试失败,进入 ㈠
acquire(1);
}

// ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
public final void acquire(int arg) {
// ㈡ tryAcquire
if (
!tryAcquire(arg) &&
// 当 tryAcquire 返回为 false 时, 先调用 addWaiter ㈣, 接着 acquireQueued ㈤
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
) {
selfInterrupt();
}
}

// ㈡ 进入 ㈢
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}

// ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果还没有获得锁
if (c == 0) {
// 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
else if (current == getExclusiveOwnerThread()) {
// state++
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 获取失败, 回到调用处
return false;
}

// ㈣ AQS 继承过来的方法, 方便阅读, 放在此处
private Node addWaiter(Node mode) {
// 将当前线程关联到一个 Node 对象上, 模式为独占模式,新建的Node的waitstatus默认为0,因为waitstatus是成员变量,默认被初始化为0
Node node = new Node(Thread.currentThread(), mode);
// 如果 tail 不为 null, cas 尝试将 Node 对象加入 AQS 队列尾部
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
// 双向链表
pred.next = node;
return node;
}
}
//如果tail为null,尝试将 Node 加入 AQS, 进入 ㈥
enq(node);
return node;
}

// ㈥ AQS 继承过来的方法, 方便阅读, 放在此处
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
// 还没有, 设置 head 为哨兵节点(不对应线程,状态为 0)
if (compareAndSetHead(new Node())) {
tail = head;
}
} else {
// cas 尝试将 Node 对象加入 AQS 队列尾部
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

// ㈤ AQS 继承过来的方法, 方便阅读, 放在此处
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取
if (p == head && tryAcquire(arg)) {
// 获取成功, 设置自己(当前线程对应的 node)为 head
setHead(node);
// 上一个节点 help GC
p.next = null;
failed = false;
// 返回中断标记 false
return interrupted;
}
if (
// 判断是否应当 park, 进入 ㈦
shouldParkAfterFailedAcquire(p, node) &&
// park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧
parkAndCheckInterrupt()
) {
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}

// ㈦ AQS 继承过来的方法, 方便阅读, 放在此处
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取上一个节点的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) {
// 上一个节点都在阻塞, 那么自己也阻塞好了
return true;
}
// > 0 表示取消状态
if (ws > 0) {
// 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 这次还没有阻塞
// 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

// ㈧ 阻塞当前线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
}

解锁流程

Thread-0 释放锁,进入 tryRelease 流程,如果成功:

  • 设置 exclusiveOwnerThread 为 null
  • state = 0

image-20210616205036686

如果当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程:

unparkSuccessor 中会找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1.

回到 Thread-1 的 acquireQueued 流程:

image-20210616205303644

如果加锁成功(没有竞争),会设置 (acquireQueued 方法中):

  • exclusiveOwnerThread 为 Thread-1,state = 1;
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread;
  • 原本的 head 因为从链表断开,而可被垃圾回收。

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了:

image-20210616205711587

如果不巧又被 Thread-4 占了先:

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1;
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞。

解锁源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// Sync 继承自 AQS
static final class NonfairSync extends Sync {
// 解锁实现
public void unlock() {
sync.release(1);
}

// AQS 继承过来的方法, 方便阅读, 放在此处
public final boolean release(int arg) {
// 尝试释放锁, 进入 ㈠
if (tryRelease(arg)) {
// 队列头节点 unpark
Node h = head;
if (
// 队列不为 null
h != null &&
// waitStatus == Node.SIGNAL 才需要 unpark
h.waitStatus != 0
) {
// unpark AQS 中等待的线程, 进入 ㈡
unparkSuccessor(h);
}
return true;
}
return false;
}

// ㈠ Sync 继承过来的方法, 方便阅读, 放在此处
protected final boolean tryRelease(int releases) {
// state--
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 支持锁重入, 只有 state 减为 0, 才释放成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

// ㈡ AQS 继承过来的方法, 方便阅读, 放在此处
private void unparkSuccessor(Node node) {
// 如果状态为 Node.SIGNAL 尝试重置状态为 0, 如果线程获取到了锁那么后来头结点会被抛弃掉
// 不成功也可以
int ws = node.waitStatus;
if (ws < 0) {
compareAndSetWaitStatus(node, ws, 0);
}
// 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的
Node s = node.next;
// 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
}

锁重入原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static final class NonfairSync extends Sync {

// Sync 继承过来的方法, 方便阅读, 放在此处
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
else if (current == getExclusiveOwnerThread()) {
// state++
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

//解锁
// Sync 继承过来的方法, 方便阅读, 放在此处
protected final boolean tryRelease(int releases) {
// state--
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 支持锁重入, 只有 state 减为 0, 才释放成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}

可打断原理

不可打断模式:在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Sync 继承自 AQS
static final class NonfairSync extends Sync {
// ...

private final boolean parkAndCheckInterrupt() {
// 如果打断标记已经是 true, 则 park 会失效
LockSupport.park(this);
// interrupted 会清除打断标记
return Thread.interrupted();
}

final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
// 还是需要获得锁后, 才能返回打断状态
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()
) {
// 如果是因为 interrupt 被唤醒, 返回打断状态为 true
interrupted = true;
}
}
}
finally {
if (failed)
cancelAcquire(node);
}
}

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
) {
// 如果打断状态为 true
selfInterrupt();
}
}

static void selfInterrupt() {
// 重新产生一次中断,这时候线程是如果正常运行的状态,那么不是出于sleep等状态,interrupt方法就不会报错
Thread.currentThread().interrupt();
}
}

可打断模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static final class NonfairSync extends Sync {
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 如果没有获得到锁, 进入 ㈠
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}

// ㈠ 可打断的获取锁流程
private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) {
// 在 park 过程中如果被 interrupt 会进入此
// 这时候抛出异常, 而不会再次进入 for (;;)
throw new InterruptedException();
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}

公平锁原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}

// AQS 继承过来的方法, 方便阅读, 放在此处
public final void acquire(int arg) {
if (
!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
) {
selfInterrupt();
}
}
// 与非公平锁主要区别在于 tryAcquire 方法的实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

// ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// h != t 时表示队列中有 Node
return h != t &&(
// (s = h.next) == null 表示队列中还有没有老二或者队列中老二线程不是此线程
(s = h.next) == null ||
s.thread != Thread.currentThread()
);
}
}

条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject。

await流程

开始时 Thread-0 持有锁,调用 await方法,进入 ConditionObject 的 addConditionWaiter 流程,创建新的 Node 节点,状态为-2(Node.CONDITION),关联 Thread-0,加入等待队列尾部。

image-20210617095324574

接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁:

image-20210617095417259

unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程竞争,那么 Thread-1 竞争成功:

image-20210617095612679

park 阻塞 Thread-0:

image-20210617095650506

signal 流程

假设 Thread-1 要来唤醒 Thread-0:

image-20210617095807467

进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node:

image-20210617100008910

执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的waitStatus 改为 -1:

image-20210617100149481

之后Thread-1 释放锁,进入 unlock 流程。

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;

// 第一个等待节点
private transient Node firstWaiter;

// 最后一个等待节点
private transient Node lastWaiter;
public ConditionObject() { }
// ㈠ 添加一个 Node 至等待队列
private Node addConditionWaiter() {
Node t = lastWaiter;
// 所有已取消的 Node 从队列链表删除, 见 ㈡
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 创建一个关联当前线程的新 Node, 添加至队列尾部
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
// 唤醒 - 将没取消的第一个节点转移至 AQS 队列
private void doSignal(Node first) {
do {
// 已经是尾节点了
if ( (firstWaiter = first.nextWaiter) == null) {
lastWaiter = null;
}
first.nextWaiter = null;
} while (
// 将等待队列中的 Node 转移至 AQS 队列, 不成功且还有节点则继续循环 ㈢
!transferForSignal(first) &&
// 队列还有节点
(first = firstWaiter) != null
);
}

// 外部类方法, 方便阅读, 放在此处
// ㈢ 如果节点状态是取消, 返回 false 表示转移失败, 否则转移成功
final boolean transferForSignal(Node node) {
// 设置当前node状态为0(因为处在队列末尾),如果状态已经不是 Node.CONDITION, 说明被取消了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 加入 AQS 队列尾部
Node p = enq(node);
int ws = p.waitStatus;
if (
// 插入节点的上一个节点被取消
ws > 0 ||
// 插入节点的上一个节点不能设置状态为 Node.SIGNAL
!compareAndSetWaitStatus(p, ws, Node.SIGNAL)
) {
// unpark 取消阻塞, 让线程重新同步状态
LockSupport.unpark(node.thread);
}
return true;
}
// 全部唤醒 - 等待队列的所有节点转移至 AQS 队列
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}

// ㈡
private void unlinkCancelledWaiters() {
// ...
}
// 唤醒 - 必须持有锁才能唤醒, 因此 doSignal 内无需考虑加锁
public final void signal() {
// 如果没有持有锁,会抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
// 全部唤醒 - 必须持有锁才能唤醒, 因此 doSignalAll 内无需考虑加锁
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
// 不可打断等待 - 直到被唤醒
public final void awaitUninterruptibly() {
// 添加一个 Node 至等待队列, 见 ㈠
Node node = addConditionWaiter();
// 释放节点持有的锁, 见 ㈣
int savedState = fullyRelease(node);
boolean interrupted = false;
// 如果该节点还没有转移至 AQS 队列, 阻塞
while (!isOnSyncQueue(node)) {
// park 阻塞
LockSupport.park(this);
// 如果被打断, 仅设置打断状态
if (Thread.interrupted())
interrupted = true;
}
// 唤醒后, 尝试竞争锁, 如果失败进入 AQS 队列
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
// 外部类方法, 方便阅读, 放在此处
// ㈣ 因为某线程可能重入,需要将 state 全部释放,获取state,然后把它全部减掉,以全部释放
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
// 唤醒等待队列队列中的下一个节点
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
// 打断模式 - 在退出等待时重新设置打断状态
private static final int REINTERRUPT = 1;
// 打断模式 - 在退出等待时抛出异常
private static final int THROW_IE = -1;
// 判断打断模式
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
// ㈤ 应用打断模式
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
// 等待 - 直到被唤醒或打断
public final void await() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
// 添加一个 Node 至等待队列, 见 ㈠
Node node = addConditionWaiter();
// 释放节点持有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果该节点还没有转移至 AQS 队列, 阻塞
while (!isOnSyncQueue(node)) {
// park 阻塞
LockSupport.park(this);
// 如果被打断, 退出等待队列
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 退出等待队列后, 还需要获得 AQS 队列的锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 所有已取消的 Node 从队列链表删除, 见 ㈡
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// 应用打断模式, 见 ㈤
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
// 等待 - 直到被唤醒或打断或超时
public final long awaitNanos(long nanosTimeout) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
// 添加一个 Node 至等待队列, 见 ㈠
Node node = addConditionWaiter();
// 释放节点持有的锁
int savedState = fullyRelease(node);
// 获得最后期限
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
// 如果该节点还没有转移至 AQS 队列, 阻塞
while (!isOnSyncQueue(node)) {
// 已超时, 退出等待队列
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
// park 阻塞一定时间, spinForTimeoutThreshold 为 1000 ns
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 如果被打断, 退出等待队列
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
// 退出等待队列后, 还需要获得 AQS 队列的锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 所有已取消的 Node 从队列链表删除, 见 ㈡
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// 应用打断模式, 见 ㈤
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
// 等待 - 直到被唤醒或打断或超时, 逻辑类似于 awaitNanos
public final boolean awaitUntil(Date deadline) throws InterruptedException {
// ...
}
// 等待 - 直到被唤醒或打断或超时, 逻辑类似于 awaitNanos
public final boolean await(long time, TimeUnit unit) throws InterruptedException {
// ...
}
// 工具方法 省略 ...
}

读写锁原理

ReentrantReadWriteLock

当读操作远远高于写操作时,这时候使用读写锁让读-读可以并发,提高性能。读-写,写-写都是相互互斥的!提供一个数据容器类,内部分别使用读锁保护数据的read()方法,写锁保护数据的write()方法 。实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Slf4j
public class ReadWriteLockTest {
public static void main(String[] args) {
DataContainer dataContainer = new DataContainer();
new Thread(() -> {
dataContainer.read();
dataContainer.write();
},"t1").start();

new Thread(() -> {
dataContainer.write();
},"t2").start();
}
}

@Slf4j
class DataContainer {
private Object object = new Object();
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

public Object read() {
readLock.lock();
log.info("拿到读锁");
try {
log.info("读取操作");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
log.info("释放读锁");
}
return object;
}

public void write() {
writeLock.lock();
log.info("拿到写锁");
try {
log.info("写操作");
}finally {
writeLock.unlock();
log.info("释放写锁");
}
}
}

输出:读读并发,读写阻塞

1
2
3
4
5
6
7
8
9
11:01:47.856 [t1] INFO com.heu.test.DataContainer - 拿到读锁
11:01:47.865 [t1] INFO com.heu.test.DataContainer - 读取操作
11:01:47.856 [t2] INFO com.heu.test.DataContainer - 拿到读锁
11:01:47.867 [t2] INFO com.heu.test.DataContainer - 读取操作
11:01:48.872 [t1] INFO com.heu.test.DataContainer - 释放读锁
11:01:48.872 [t2] INFO com.heu.test.DataContainer - 释放读锁
11:01:48.872 [t1] INFO com.heu.test.DataContainer - 拿到写锁
11:01:48.872 [t1] INFO com.heu.test.DataContainer - 写操作
11:01:48.872 [t1] INFO com.heu.test.DataContainer - 释放写锁

注意事项:

  1. 读锁不支持条件变量。
  2. 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待。
1
2
3
4
5
6
7
8
9
10
11
12
13
r.lock();
try {
// ...
w.lock();
try {
// ...
} finally{
w.unlock();
}
} finally{
r.unlock();
}
//写锁永久等待
  1. 重入时降级支持:即持有写锁的情况下去获取读锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class CachedData {
Object data;
// 是否有效,如果失效,需要重新计算 data
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// 获取写锁前必须释放读锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新
if (!cacheValid) {
data = ...
cacheValid = true;
}
// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存
rwl.readLock().lock();
} finally {

rwl.writeLock().unlock();
}
}
// 自己用完数据, 释放读锁
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}

读写锁原理

读写锁用的是同一个 Sync 同步器,因此等待队列、state 等也是同一个。

下面执行:t1 w.lock,t2 r.lock 情况

  1. t1 成功上锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,读锁使用的是 state 的高 16 位。

image-20210617141553828

  1. t2 执行 r.lock,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败。tryAcquireShared 返回值表示:

    • -1 表示失败。
    • 0 表示成功,但后继节点不会继续唤醒。
    • 正数表示成功,而且数值是还有几个后继节点需要唤醒,这里的读写锁返回 1。image-20210617141820347
  2. 这时会进入 sync.doAcquireShared(1) 流程,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态。image-20210617142341800

  3. t2 会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared(1) 来尝试获取锁。

  4. 如果没有成功,在 doAcquireShared 内 for 循环一次,把前驱节点的 waitStatus 改为 -1,再 for 循环一 次尝试 tryAcquireShared(1) 如果还不成功,那么在 parkAndCheckInterrupt() 处 park。

    image-20210617142418723

又继续执行 :t3 r.lock,t4 w.lock。在这种状态下,假设又有 t3 加读锁和 t4 加写锁,这期间 t1 仍然持有锁,就变成了下面的样子:

image-20210617144002185

继续执行 t1 w.unlock,这时会走到写锁的 sync.release(1) 流程,调用 sync.tryRelease(1) 成功,变成下面的样子:

image-20210617144632809

接下来执行唤醒流程 sync.unparkSuccessor,即让老二恢复运行。这时 t2 在 doAcquireShared 内 parkAndCheckInterrupt() 处恢复运行,图中的t2从黑色变成了蓝色(注意这里只是恢复运行而已,并没有获取到锁!) 这回再来一次 for (;; ) 执行 tryAcquireShared 成功,让读锁计数加一。

image-20210617144832160

这时 t2 已经恢复运行,接下来 t2 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点。

image-20210617144926677

事情还没完,在 setHeadAndPropagate 方法内还会检查下一个节点是否是 shared,如果是,则调用 doReleaseShared() 将 head 的状态从 -1 改为 0 并唤醒老二,这时 t3 在 doAcquireShared 内 parkAndCheckInterrupt() 处恢复运行。
image-20210617145201826

这回再来一次 for (;; ) ,执行 tryAcquireShared 成功则让读锁计数加一。

image-20210617145259018

这时 t3 已经恢复运行,接下来 t3 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点。image-20210617145411846

再继续执行t2 r.unlock,t3 r.unlock t2,进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一。image-20210617145742494

但由于计数还不为零,t3 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,这回计数为零了,进入 doReleaseShared() 将头节点从 -1 改为 0 并唤醒老二,即:image-20210617150145779

之后 t4线程 在 acquireQueued 中 parkAndCheckInterrupt 处恢复运行,再次 执行for (;; )循环, 这次自己是老二,并且没有其他线程竞争,tryAcquire(1) 成功,修改头结点,流程结束。image-20210617150310448

源码分析

写锁源码分析

写锁上锁流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static final class NonfairSync extends Sync {
// ... 省略无关代码

// 外部类 WriteLock 方法, 方便阅读, 放在此处
public void lock() {
sync.acquire(1);
}

// AQS 继承过来的方法, 方便阅读, 放在此处
public final void acquire(int arg) {
if (
// 尝试获得写锁失败
!tryAcquire(arg) &&
// 将当前线程关联到一个 Node 对象上, 模式为独占模式
// 进入 AQS 队列阻塞
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
) {
selfInterrupt();
}
}

// Sync 继承过来的方法, 方便阅读, 放在此处
protected final boolean tryAcquire(int acquires) {
// 获得低 16 位, 代表写锁的 state 计数
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);

if (c != 0) {
if (
// c != 0 and w == 0 表示有读锁返回错误,读锁不支持锁升级, 或者
w == 0 ||
// c != 0 and w == 0 表示有写,如果 exclusiveOwnerThread 不是自己
current != getExclusiveOwnerThread()
) {
// 获得锁失败
return false;
}
// 写锁计数超过低 16 位, 报异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 写锁重入, 获得锁成功
setState(c + acquires);
return true;
}
if (
// 判断写锁是否该阻塞这里返回false, 或者
writerShouldBlock() ||
// 尝试更改计数失败
!compareAndSetState(c, c + acquires)
) {
// 获得锁失败
return false;
}
// 获得锁成功
setExclusiveOwnerThread(current);
return true;
}

// 非公平锁 writerShouldBlock 总是返回 false, 无需阻塞
final boolean writerShouldBlock() {
return false;
}
}

写锁释放流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static final class NonfairSync extends Sync {
// ... 省略无关代码

// WriteLock 方法, 方便阅读, 放在此处
public void unlock() {
sync.release(1);
}

// AQS 继承过来的方法, 方便阅读, 放在此处
public final boolean release(int arg) {
// 尝试释放写锁成功
if (tryRelease(arg)) {
// unpark AQS 中等待的线程
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

// Sync 继承过来的方法, 方便阅读, 放在此处
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
// 因为可重入的原因, 写锁计数为 0, 才算释放成功
boolean free = exclusiveCount(nextc) == 0;
if (free) {
setExclusiveOwnerThread(null);
}
setState(nextc);
return free;
}
}

读锁源码分析

读锁上锁流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
static final class NonfairSync extends Sync {

// ReadLock 方法, 方便阅读, 放在此处
public void lock() {
sync.acquireShared(1);
}

// AQS 继承过来的方法, 方便阅读, 放在此处
public final void acquireShared(int arg) {
// tryAcquireShared 返回负数, 表示获取读锁失败
if (tryAcquireShared(arg) < 0) {
doAcquireShared(arg);
}
}

// Sync 继承过来的方法, 方便阅读, 放在此处
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果是其它线程持有写锁, 获取读锁失败
if (
exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current
) {
return -1;
}
int r = sharedCount(c);
if (
// 读锁不该阻塞(如果老二是写锁,读锁该阻塞), 并且
!readerShouldBlock() &&
// 小于读锁计数, 并且
r < MAX_COUNT &&
// 尝试增加计数成功
compareAndSetState(c, c + SHARED_UNIT)
) {
// ... 省略不重要的代码
return 1;
}
return fullTryAcquireShared(current);
}

// 非公平锁 readerShouldBlock 看 AQS 队列中第一个节点是否是写锁
// true 则该阻塞, false 则不阻塞
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}

// AQS 继承过来的方法, 方便阅读, 放在此处
// 与 tryAcquireShared 功能类似, 但会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {
// ... 省略不重要的代码
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
// ... 省略不重要的代码
return 1;
}
}
}

// AQS 继承过来的方法, 方便阅读, 放在此处
private void doAcquireShared(int arg) {
// 将当前线程关联到一个 Node 对象上, 模式为共享模式
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 再一次尝试获取读锁
int r = tryAcquireShared(arg);
// 成功
if (r >= 0) {
// ㈠
// r 表示可用资源数, 在这里总是 1 允许传播
//(唤醒 AQS 中下一个 Share 节点)
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (
// 是否在获取读锁失败时阻塞(前一个阶段 waitStatus == Node.SIGNAL)
shouldParkAfterFailedAcquire(p, node) &&
// park 当前线程
parkAndCheckInterrupt()
) {
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}

// ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
// 设置自己为 head
setHead(node);

// propagate 表示有共享资源(例如共享读锁或信号量)
// 原 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
// 现在 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 如果是最后一个节点或者是等待共享读锁的节点
if (s == null || s.isShared()) {
// 进入 ㈡
doReleaseShared();
}
}
}

// ㈡ AQS 继承过来的方法, 方便阅读, 放在此处
private void doReleaseShared() {
// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
// 如果 head.waitStatus == 0 ==> Node.PROPAGATE
for (;;) {
Node h = head;
// 队列还有节点
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 下一个节点 unpark 如果成功获取读锁
// 并且下下个节点还是 shared, 继续 doReleaseShared
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
}

读锁释放流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
static final class NonfairSync extends Sync {

// ReadLock 方法, 方便阅读, 放在此处
public void unlock() {
sync.releaseShared(1);
}

// AQS 继承过来的方法, 方便阅读, 放在此处
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

// Sync 继承过来的方法, 方便阅读, 放在此处
protected final boolean tryReleaseShared(int unused) {
// ... 省略不重要的代码
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc)) {
// 读锁的计数不会影响其它获取读锁线程, 但会影响其它获取写锁线程
// 计数为 0 才是真正释放
return nextc == 0;
}
}
}

// AQS 继承过来的方法, 方便阅读, 放在此处
private void doReleaseShared() {
// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
// 如果 head.waitStatus == 0 ==> Node.PROPAGATE
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果有其它线程也在释放读锁,那么需要将 waitStatus 先改为 0
// 防止 unparkSuccessor 被多次执行
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 如果已经是 0 了,改为 -3,用来解决传播性,见后文信号量 bug 分析
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
}

StampedLock

该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用。

加解读锁

1
2
long stamp = lock.readLock();
lock.unlockRead(stamp);

加解写锁

1
2
long stamp = lock.writeLock();
lock.unlockWrite(stamp);

StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次戳校验,如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。

1
2
3
4
5
long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
// 锁升级
}

示例:提供一个数据容器类内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法。代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Slf4j
public class StampedLockTest {
public static void main(String[] args) throws InterruptedException {
StampedLockDataContainer dataContainer = new StampedLockDataContainer(1);
Thread t1 = new Thread(() -> {
try {
System.out.println(dataContainer.read(1));
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");

t1.start();

TimeUnit.MILLISECONDS.sleep(500);
Thread t2 = new Thread(() -> {
dataContainer.write(10);
}, "t2");

t2.start();
}
}

@Slf4j
class StampedLockDataContainer {
private int data;
private StampedLock stampedLock = new StampedLock();

public StampedLockDataContainer(int data) {
this.data = data;
}

public int read(int readTime) throws InterruptedException {
long stamp = stampedLock.tryOptimisticRead();
log.info("optimistic read locking ...{}", stamp);
Thread.sleep(readTime * 1000);
if (stampedLock.validate(stamp)) {
log.info("read finish... {}", stamp);
return data;
}

// 锁升级 - 读锁
log.info("update to read lock ...");
try {
stamp = stampedLock.readLock();
log.info("read lock {}", stamp);
Thread.sleep(readTime * 1000);
log.info("read finish ... {}", stamp);
return data;
}finally {
stampedLock.unlockRead(stamp);
}
}

public void write(int newData) {
long stamp = stampedLock.writeLock();
try {
log.info("write lock {}", stamp);
this.data = newData;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("write finish ... {}", stamp);
log.info("write newData ... {}", this.data);
} finally {
stampedLock.unlockWrite(stamp);
}
}
}

输出:

1
2
3
4
5
6
7
8
16:30:13.011 [t1] INFO com.heu.test.StampedLockDataContainer - optimistic read locking ...256
16:30:13.519 [t2] INFO com.heu.test.StampedLockDataContainer - write lock 384
16:30:14.031 [t1] INFO com.heu.test.StampedLockDataContainer - update to read lock ...
16:30:14.523 [t2] INFO com.heu.test.StampedLockDataContainer - write finish ... 384
16:30:14.524 [t2] INFO com.heu.test.StampedLockDataContainer - write newData ... 10
16:30:14.525 [t1] INFO com.heu.test.StampedLockDataContainer - read lock 513
16:30:15.533 [t1] INFO com.heu.test.StampedLockDataContainer - read finish ... 513
10

注意:

  1. StampedLock 不支持条件变量
  2. StampedLock 不支持可重入

Semaphore

基本使用

信号量,用来限制能同时访问共享资源的线程上限。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Slf4j
public class SemaphoreTest {
public static void main(String[] args) {
//1.创建一个对象
Semaphore semaphore = new Semaphore(3);

//2.开10个线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//获取一个许可
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}

try {
log.info("start...");
Thread.sleep(1000);
log.info("end...");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},"t"+(i+1)).start();
}

}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
16:43:36.143 [t2] INFO com.heu.test.SemaphoreTest - start...
16:43:36.143 [t3] INFO com.heu.test.SemaphoreTest - start...
16:43:36.143 [t1] INFO com.heu.test.SemaphoreTest - start...
16:43:37.154 [t1] INFO com.heu.test.SemaphoreTest - end...
16:43:37.154 [t3] INFO com.heu.test.SemaphoreTest - end...
16:43:37.155 [t2] INFO com.heu.test.SemaphoreTest - end...
16:43:37.156 [t4] INFO com.heu.test.SemaphoreTest - start...
16:43:37.155 [t10] INFO com.heu.test.SemaphoreTest - start...
16:43:37.156 [t6] INFO com.heu.test.SemaphoreTest - start...
16:43:38.162 [t4] INFO com.heu.test.SemaphoreTest - end...
16:43:38.162 [t6] INFO com.heu.test.SemaphoreTest - end...
16:43:38.162 [t10] INFO com.heu.test.SemaphoreTest - end...
16:43:38.162 [t9] INFO com.heu.test.SemaphoreTest - start...
16:43:38.163 [t7] INFO com.heu.test.SemaphoreTest - start...
16:43:38.163 [t8] INFO com.heu.test.SemaphoreTest - start...
16:43:39.174 [t9] INFO com.heu.test.SemaphoreTest - end...
16:43:39.174 [t7] INFO com.heu.test.SemaphoreTest - end...
16:43:39.174 [t5] INFO com.heu.test.SemaphoreTest - start...
16:43:39.174 [t8] INFO com.heu.test.SemaphoreTest - end...
16:43:40.181 [t5] INFO com.heu.test.SemaphoreTest - end...

Semaphore原理

Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像是获得了停车位,然后停车场显示空余车位减一。如下示例:刚开始 permits(state)为 3,这时 5 个线程来获取资源:

image-20210617164935238

假设其中 Thread-1,Thread-2,Thread-4 cas 竞争成功,而 Thread-0 和 Thread-3 竞争失败,进入 AQS 队列park 阻塞。

image-20210617165100987

这时 Thread-4 释放了 permits,状态如下:

image-20210617165142832

接下来 Thread-0 竞争成功,permits 再次设置为 0,设置自己为 head 节点,断开原来的 head 节点,unpark 接下来的 Thread-3 节点,但由于 permits 是 0,因此 Thread-3 在尝试不成功后再次进入 park 状态。

image-20210617165256132

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
// permits 即 state
super(permits);
}

// Semaphore 方法, 方便阅读, 放在此处
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// AQS 继承过来的方法, 方便阅读, 放在此处
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

// 尝试获得共享锁
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}

// Sync 继承过来的方法, 方便阅读, 放在此处
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (
// 如果许可已经用完, 返回负数, 表示获取失败, 进入 doAcquireSharedInterruptibly
remaining < 0 ||
// 如果 cas 重试成功, 返回正数, 表示获取成功
compareAndSetState(available, remaining)
) {
return remaining;
}
}
}

// AQS 继承过来的方法, 方便阅读, 放在此处
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 再次尝试获取许可
int r = tryAcquireShared(arg);
if (r >= 0) {
// 成功后本线程出队(AQS), 所在 Node设置为 head
// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
// 如果 head.waitStatus == 0 ==> Node.PROPAGATE
// r 表示可用资源数, 为 0 则不会继续传播
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 不成功, 设置上一个节点 waitStatus = Node.SIGNAL, 下轮进入 park 阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

// Semaphore 方法, 方便阅读, 放在此处
public void release() {
sync.releaseShared(1);
}

// AQS 继承过来的方法, 方便阅读, 放在此处
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

// Sync 继承过来的方法, 方便阅读, 放在此处
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
}

CountdownLatch

CountDownLatch 允许多线程阻塞在一个地方,直至所有线程的任务都执行完毕。在 Java 并发中,countdownlatch 的概念是一个常见的面试题,所以一定要确保很好的理解了它。

  • CountDownLatch 是共享锁的一种实现,它默认构造 AQS 的 state 值为 count。当线程使用 countDown方法时,其实使用了 tryReleaseShared 方法以CAS 的操作来减少 state,当 state 为 0, 就代表所有的线程都调用了countDown方法。
  • 当调用 await 方法的时候,如果 state 不为0,就代表仍然有线程没有调用 countDown0 方法,那么就把已经调用过 countDown 的线程都放入阻塞队列 Park ,并自旋 CAS 判断 state == 0,直至最后一个线程调用了 countDown ,使得 state == 0,于是阻塞的线程便判断成功,全部往下执行。

用来进行线程同步协作,等待所有线程完成倒计时。 其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@Slf4j
@SuppressWarnings("all")
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
method3();
}

public static void method1() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(() -> {
log.info("t1 start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("t1 end ...");
countDownLatch.countDown();
},"t1").start();

new Thread(() -> {
log.info("t2 start ...");

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("t2 end ...");
countDownLatch.countDown();
}, "t2").start();

new Thread(() -> {
log.info("t3 start ...");

try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("t3 end ...");
countDownLatch.countDown();
}, "t3").start();

log.info("main wait ...");
countDownLatch.await();
log.info("main wait end ...");
}

public static void method2() {
CountDownLatch countDownLatch = new CountDownLatch(3);
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.submit(() -> {
log.info("t1 start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
log.info("t1 end ...{}", countDownLatch.getCount());
});

executorService.submit(() -> {
log.info("t2 start ...");

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("t2 end ...{}", countDownLatch.getCount());
countDownLatch.countDown();
});

executorService.submit(() -> {
log.info("t3 start ...");

try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("t3 end ...{}", countDownLatch.getCount());
countDownLatch.countDown();
});

executorService.submit(() -> {
log.info("main wait...");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("main wait end ...");
executorService.shutdown();
});
}

public static void method3() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
ExecutorService executorService = Executors.newFixedThreadPool(10);
String[] all = new String[10];
Random random = new Random();
for (int i = 0; i < 10; i++) {
int id = i;
executorService.submit(() -> {
for (int j = 0; j <= 100; j++) {
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
all[id] = j + "%";
System.out.print("\r" + Arrays.toString(all));
}
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println();
System.out.println("游戏开始");
executorService.shutdown();
}
}

method1 输出:

1
2
3
4
5
6
7
8
17:18:12.575 [main] INFO com.heu.test.CountDownLatchTest - main wait ...
17:18:12.575 [t2] INFO com.heu.test.CountDownLatchTest - t2 start ...
17:18:12.575 [t1] INFO com.heu.test.CountDownLatchTest - t1 start ...
17:18:12.575 [t3] INFO com.heu.test.CountDownLatchTest - t3 start ...
17:18:13.592 [t1] INFO com.heu.test.CountDownLatchTest - t1 end ...
17:18:14.098 [t3] INFO com.heu.test.CountDownLatchTest - t3 end ...
17:18:14.590 [t2] INFO com.heu.test.CountDownLatchTest - t2 end ...
17:18:14.590 [main] INFO com.heu.test.CountDownLatchTest - main wait end ...

method2 输出:

1
2
3
4
5
6
7
8
17:23:07.829 [pool-1-thread-4] INFO com.heu.test.CountDownLatchTest - main wait...
17:23:07.829 [pool-1-thread-2] INFO com.heu.test.CountDownLatchTest - t2 start ...
17:23:07.829 [pool-1-thread-3] INFO com.heu.test.CountDownLatchTest - t3 start ...
17:23:07.829 [pool-1-thread-1] INFO com.heu.test.CountDownLatchTest - t1 start ...
17:23:08.854 [pool-1-thread-1] INFO com.heu.test.CountDownLatchTest - t1 end ...2
17:23:09.344 [pool-1-thread-3] INFO com.heu.test.CountDownLatchTest - t3 end ...2
17:23:09.851 [pool-1-thread-2] INFO com.heu.test.CountDownLatchTest - t2 end ...1
17:23:09.851 [pool-1-thread-4] INFO com.heu.test.CountDownLatchTest - main wait end ...

method3 输出:当所有线程全部加载完毕,输出游戏开始,类似王者荣耀!

1
2
[100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%]
游戏开始

CyclicBarrier

CyclicBarri[ˈsaɪklɪk ˈbæriɚ]:循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行。跟 CountdownLatch 一样,但这个可以重用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) {

ExecutorService executorService = Executors.newFixedThreadPool(2);
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
log.info("task2 finish ...");
});

for(int i = 0; i < 3; i++) {
executorService.submit(() -> {
log.info("task1 begin ...");
try {
Thread.sleep(1000);
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
executorService.submit(() -> {
log.info("task2 begin ...");
try {
Thread.sleep(2000);
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
posted @ 2023-04-13 17:07  maxzhangxiaotao  阅读(36)  评论(0编辑  收藏  举报