0%

JUC之ReentrantLock解读

ReentrantLock简介

ReentrantLock是可重入锁的实现,可重入锁的含义是:如果已经拥有锁的线程再次获取锁时会立即响应成功,这点可以使用isHeldByCurrentThreadgetHoldCount方法来检验。ReentrantLock可重入互斥锁具有同synchronized的隐式监视器锁相同的基本行为和语义,但是其更具有扩展能力。

ReentrantLock提供公平锁和非公平锁的特性,如果设置为公平锁,那么锁倾向于访问等待时间最长的线程;如果是非公平锁,那便不会保证任何特定的访问顺序。公平锁会导致整体的吞吐量降低,不过却可以在最少的时间差内获取到锁和保证锁饥饿。不过请注意,锁是公平并不能保证线程调度的公平性。

ReentrantLock使用

典型用法如下(这里以lock为例,当然也可以用tryLock方法来获取锁):

1
2
3
4
5
6
7
8
9
10
11
12
13
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...

public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}

ReentrantLock源码剖析

源码中涉及AQS模板类的调用,可选择先查看《JUC之AQS解读》这篇文章的介绍。

同步器

对于同步状态,依然是继承与AQS来实现的,考虑到有公平锁和非公平锁的特性,其又做了一层抽象,提供一些共用方法,如:

对于lock的加锁过程则交予具体的锁类型同步器去实现

  • 获取非公平锁: nonfairTryAcquire
  • 释放锁:tryRelease
  • 是否当前线程持有锁:isHeldExclusively
  • 条件对象:newCondition
  • 对象序列化:readObject
  • 线程持锁状态:getOwner,getHoldCount,isLocked等

其中获取非公平锁方式放在这个抽象层的原因是为了ReentrantLock在默认非公平性下调用:

1
2
3
public boolean tryLock() {
return sync.nonfairTryAcquire(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
33
34
35
36
37
38
39
40
abstract static class Sync extends AbstractQueuedSynchronizer {

abstract void lock();

final boolean nonfairTryAcquire(int acquires) {
// 省略
}

protected final boolean tryRelease(int releases) {
// 省略
}

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();
}

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

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

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

private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}

公平锁与非公平锁

ReentrantLock公平锁和非公平锁的特性可以通过选择如下构造函数来设定:

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

如果直接使用默认的无参构造函数,那么是用的非公平锁:

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

公平锁和非公平锁的同步状态器源码如下:

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
static final class NonfairSync extends Sync {

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThreadcurrentThread(Thread.());
else
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {

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

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;
}
}

同时结合前面的抽象同步器代码,可以总结出区别在于:

  • 非公平的加锁在最开始阶段会进行一次资源竞争抢占compareAndSetState(0, 1),如果抢占成功则直接加上锁。
  • 公平性加锁在尝试获取锁的时候,如果state==0的条件下多了一层判断!hasQueuedPredecessors()来保证公平有序。

hasQueuedPredecessors的作用是判断是否有其他线程先于当前线程进行等待,避免非公平抢占。其源码如下,通过判断是否有等待节点来告知:

1
2
3
4
5
6
7
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

可重入性

对于可重入性,就是在判断到当前线程持有锁的时候,再将状态值进行加法:

1
2
3
4
5
6
7
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}

对于可重入的次数而言,由于继承的是AQS并使用其state字段,所以最大值只能到Integer.MAX_VALUE,超出此限制会抛出Error

1
2
3
4
/**
* The synchronization state.
*/
private volatile int state;

锁释放

回到抽象同步器的tryRelease方法,这是对锁释放的一个操作,其完成的功能有如下3步:

  1. 判断是否是当前线程,不是则抛异常;
  2. 释放状态值:state - releases;
  3. 如果状态值为0,则释放排他锁;
  4. 设置状态值并返回;
1
2
3
4
5
6
7
8
9
10
11
12
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

根据AQS模板类的使用,会根据如下调用流程走完锁释放流程:

1
ReentrantLock.unlock -> aqs.release -> ReentrantLock.tryRelease