前言

AQS是整个JUC的基石,全称为AbstractQueuedSynchronizer,中文翻译为抽象队列同步器

image-20230223201825392

AQS是一个抽象类,运用的设计模式是抽象模版模式,有许多的锁和同步器实现了AQS

其中ReentrantLock就实现了AQS,这里以ReentrantLock为例来剖析AQS

ReentrantLock内部结构

ReentrantLock类实现了Lock接口

image-20230223203815464

ReentrantLock中有一个抽象的静态内部类Sync,这个sync继承了AbstractQueuedSynchronizer

image-20230223202934536

sync下面有两个实现类

分别为NonfairSync和fairSync

NonfairSync用于实现ReentrantLock非公平锁功能

image-20230223203027880

FairSync用于实现ReentrantLock公平锁功能

image-20230223203150299

ReentrantLock的主要功能是由这个静态内部类Sync实现的

ReentrantLock中有一个成员变量sync,ReentrantLock的方法最终是通过调用这个sync成员变量实现的

image-20230223203422709

而ReentrantLock是非公平锁还是公平锁最终看sync接收的是NonfairSync对象还是FairSync对象

用一张图来概括以上关系

image-20230223205155330

ReentrantLock构造方法

ReentrantLock有两个构造方法

分别如下所示

无参构造

默认为非公平锁

image-20230223202219519

有参构造

根据传入的fair值选择公平锁还是非公平锁

true则为公平锁,false则为非公平锁

image-20230223202244536

lock方法源码分析

FairSync和NonfairSync的lock方法有差别

FairSync的lock方法

image-20230223210145883

NonfairSync的lock方法

image-20230223210206803

其中acquire方法为AQS的final方法

image-20230223210323437

acquire方法首先调用的是tryAcquire方法

tryAcquire方法如下

此方法为AQS的一个钩子函数,交由子类实现

image-20230223210552849

由于子类FairSync和NonfairSync的实现都不一样,所以这里以非公平锁来举例

这里以如下一段程序举例

A,B,C三个线程同时争抢ReentrantLock锁,由于抢夺有先后顺序,所以我们可以通过调试看到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
public class ReentrantLockDebug {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("A线程已完成");
}
}, "A").start();

new Thread(() -> {
lock.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("B线程已完成");
}
}, "B").start();

new Thread(() -> {
lock.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("C线程已完成");
}
}, "C").start();

}
}

ReentrantLock内部维护了一个改进的双向CLH队列,同时维护了一个状态量state,这个state用于表示当前队列的状态,这个队列被称为等待队列

队列的每一个元素都是Node类型,基本结构如下

image-20230225104941717

现在开始Debug

首先state=0,表示等待队列处于空闲状态

image-20230225105629602

然后ReentrantLock会调用sync.lock()方法进行上锁

image-20230225105727732

由于默认是非公平锁,所以进入到NonfairSync的lock方法中

首先会尝试进行一次CAS抢占锁操作

image-20230225105757476

可以看到compareAndSetState方法就是通过CAS尝试设置state=1达到上锁效果

底层是通过unsafe类实现的,这里不做深究

image-20230225110016412

由于此时state=0,s所以此时compareAndSetState设置state=1成功,成功进入if语句块里面

image-20230225110209892

接下来会调用setExclusiveOwnerThread将当前线程设置为独占线程

image-20230225110354011

exclusiveOwnerThread是AQS的一个成员变量,用于记录当前等待队列中的独占线程

image-20230225110634586

至此,第一个线程就成功抢占锁资源

接下来我们观察第二个线程的运行情况

CAS失败,进入acquire方法

image-20230225114943665

acquire方法如下

image-20230225115028419

执行tryAcquire方法

image-20230225115315850

进入nonfairTryAcquire方法

image-20230225115424442

此时state又变为0,表示当前队列中的线程已经将锁释放,多线程环境,调试过程中其他线程也在执行,所以这个方法也要进行CAS获取锁操作

image-20230225120045029

可以看到此时控制台已经输出

image-20230225115933227

可以看到获取锁成功,返回true

image-20230225120451216

回到acquire方法

image-20230225120913738

由于获得锁的线程执行很快,因此第三个线程执行的操作和第二个线程执行的操作一样。。。

还有两个重要方法acquireQueued和addWaiter程序没有执行到

因此下面分析下这两个方法的作用

addWaiter方法

addWaiter方法主要用于添加等待线程到等待队列中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;

acquireQueued方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}