Java并发编程之7——AQS如何实现ReentrantLock

ReentrantLock是显示锁,Synchronized叫内置锁是JDK1.6中由道格李引入的。他们的关系就像 wait/notify 和 condition。RenentrantLock的实现和CountDownLatch的实现基本相同,唯一的区别是ReentrantLock有两种实现,分别对应公平锁和非公平锁。

非公平锁实现源码分析

ReentrantLock默认提供的是非公平锁的实现,在默认的构造方法中实例化了ReentrantLock的实例,此时AQS的同步状态被赋初始值0,表示现在锁是可以获取的。

构造方法

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

非公平锁获取源码分析

lock方法在Sync类中被定义为抽象方法,具体的实现由公平锁和非公平锁去实现。

1
2
3
4
5
6
7
8
9
10
锁的获取
final void lock() {
不管锁是否被获取先CAS尝试获取锁
if (compareAndSetState(0, 1))
如果获取到锁之后,就设置当前线程表示当前线程获取到了锁
setExclusiveOwnerThread(Thread.currentThread());
else
如果CAS失败表示锁已经被其他的线程获取到了,就构建等待队列,将当前线程添加到队尾
acquire(1);
}

acquire的具体实现如下:

1
2
3
4
5
6
7
public final void acquire(int arg) {
if (!tryAcquire(arg) && //先CAS尝试获取一次锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){//addWaiter就是以当前线程构造节点添加到等待队列,acquireQuqued中会再次尝试获取一次锁,注意获取失败才返回false
两次获取失败 就将当前线程挂起
selfInterrupt();
}
}

NonfairSync的tryAcquire实现如下:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
非公平的方式获取一次锁
final boolean nonfairTryAcquire(int acquires) {
获取当前线程
final Thread current = Thread.currentThread();
int c = getState();
如果状态为0表示锁是可以获取的,此时不管有没有等待队列,直接进行CAS操作尝试获取到锁
if (c == 0) {
CAS获取到锁,如果获取成功就,返回true并设置当前线程获取到锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { 如果状态不为0表示锁已经被获取到了,就判断锁是否被当前线程获取到了(可重入锁),如果是当前线程获取到了锁,就将锁计数加一
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

acquireQueued方法是AQS的典型实现,在AQS源码分析文章中已经讲过。作用是将线程添加到等待队列。

注意:这里返回true表示,获取当前线程再次尝试获取锁失败。或者可以任务将当前线程添加到等待队列成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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; // help GC
failed = false;
return interrupted;
}
这里会将Node节点的状态由默认的0更新为-1
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

非公平锁释放源码分析

释放锁调用的是AQS类的release方法

1
2
3
public void unlock() {
sync.release(1);
}

1
2
3
4
5
6
7
8
9
10
public final boolean release(int arg) {
//首先调用子类的release方法,如果成功,就激活等待锁的其他的节点,然后返回。
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

我们来看Lock类是如何实现tryRelease方法的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected final boolean tryRelease(int releases) {
//首先将同步状态减一
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果不是可重入锁,这里状态就会为0,然后返回即可
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//如果是可重入锁 就CAS将锁的同步状态减一。
setState(c);
return free;
}

公平锁源码分析

公平锁构造函数

根据传入的参数,构造公平锁和非公平锁

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

公平锁获取源码分析

公平锁的获取和非公平锁的获取的区别是:

  • 非公平锁,不会判断是否有等待队列尝试获取锁,如果失败,会加入到等待队列的末尾
  • 公平锁,会判断是否有等待队列,如果有,就会将自己加入等待队列
    以下两个步骤和非公平锁一样调用的都是AQS的方法。
    1
    2
    3
    final void lock() {
    acquire(1);
    }
1
2
3
4
5
6
获取同步状态
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

公平锁和非公平锁的唯一区别是tryAcquire方法。方法的具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}

公平锁释放源码分析

公平锁的释放和非公平锁的释放过程,完全一样。