Java 并发编程之 ReentrantLock


ReentrantLock,可重入锁,基于 AQS 的机制,实现了公平锁和非公平锁,给开发者提供了一个更为灵活的锁实现,既然是个锁,ReentrantLock 也实现了 Lock 接口

先看一下 ReentrantLock 的源码的结构
reentrantlock.png

由上可见其实 ReentrantLock 无非是实现了 Lock 接口,在 Lock 接口的方法中通过 sync 对象调用相对应的方法并返回值,所以我们重点来看一下这个 sync 对象。

ReentrantLock 中,Snyc 也是一个抽象类,且是 AQS 的子类,并有两个实现类,FairSyncNonFairSync,从名字可以看出来,一个是公平锁,另一个是非公平锁。

先看 Sycn 这个抽象类

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
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;

/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
//抽象方法,由子类自行实现如何 lock
abstract void lock();

/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
/**
* 非公平的尝试获取锁
* 一般来说,tryAcquire 由子类自行实现的,但 ReentrantLock 中的
* tryLock 方法中都需要调用该非公平的尝试锁方法
* 返回值表示是否获取锁状态成功
*/
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前的锁状态
int c = getState();
if (c == 0) {
//如果 c == 0,即锁为「空闲」状态,尝试通过 cas 的方式修改所状态
if (compareAndSetState(0, acquires)) {
//cas 成功,则修改占用了锁的线程的值为当前线程,并返回获取锁状态成功
setExclusiveOwnerThread(current);
return true;
}
}
//else 说明 c != 0,说明锁已经被某个线程占用了
//则判断被占用的线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
//如果锁被占用的线程是当前线程
//如果 nextc < 0 说明超过 Int 的最大值溢出了,则抛出异常
//否则将 state 的值修改为原来的值 + acquires 的值
//这里不需要用 cas 的方式修改 state 的值是因为 if 中 已经判断了当前线程,所以不存在竞争
//最后返回 true 表示获取锁成功(即获取重入锁成功)
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//走到这里说明当前锁状态被别的线程占领了,则返回 false
return false;
}

/**
* 尝试释放锁状态
* 返回值表示是否完全释放了锁状态
*/
protected final boolean tryRelease(int releases) {
//根据当前 state 和 releases 的值相减得到新的 state 值 c
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
//如果当前线程非占用了锁的线程,并且来释放锁,则抛出异常
throw new IllegalMonitorStateException();
//初始化 free 变量为 false,只有在当前线程完全将锁释放了之后才会置为 true(因为有可重入的状态)
boolean free = false;
if (c == 0) {
free = true;
//更改占用锁的线程为 null
setExclusiveOwnerThread(null);
}
//更新 state 的值
setState(c);
//返回 free
return free;
}

protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}

final ConditionObject newCondition() {
return new ConditionObject();
}

// Methods relayed from outer class

final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}

final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}

final boolean isLocked() {
return getState() != 0;
}

/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}

接着看 Sync 的两个子类 FairSyncNonFairSync

先来看非公平的 NonFairSync,非公平同步方式的 lock 方法,会先直接去修改 state ,如果修改成功则获取到锁,将当前线程的引用保存起来,否则则调用 acquire() 方法去获取锁,而从 AQS 中我们知道 acquire() 方法中先会调用 tryAcquire() 方法先尝试获取锁

从这里也可以看到,在非公平同步方式中,无论是哪个线程先调用了 lock() 方法都会先直接调用 cas 的方法修改 state 从而获取锁,获取不到再去排队,这也就是「非公平」的体现,后来的线程可以插队抢锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;

/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

再看 FairSync

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
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1);
}

/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}

lock 方法即调用了 AQS#acquire() 方法,如果能获取到锁则修改状态,如果获取不到锁则排队等待

同理 acquire() 方法中也会调用 tryAcquire 方法,而 FairSync#tryAcquire() 的实现和 NonFairSync#tryAcquire 调用的 nonfairTryAcquire() 方法几乎是一样的,仅仅在当 当前 state == 0 的时候多加了一个判断 !hasQueuedPredecessors()

hasQueuedPredecessors() 这个方法的返回 true 表示等待队列不为空,且当前线程前面还有在排队的线程,否则返回 false

所以 !hasQueuedPredecessors() 也就表示当前线程前面没有线程在排队,才会走后续的 cas 修改锁状态等操作,这也正是 公平 的体现,后来的线程必须排队拿锁

作者

PPTing

发布于

2022-02-24

更新于

2022-04-12

许可协议

评论