AbstractQueuedSynchronizer之AQS

12、AbstractQueuedSynchronizer之AQS

12.1 AQS源代码

12.2 官网解释

12.3 整体就是一个抽象的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态

12.4 和AQS有关的

12.4.1 ReentrantLock

12.4.2 CountDownLatch

12.4.3 ReentrantReadWriteLock

12.4.4 Semaphore

12.5 解释说明

  • 抢到资源的线程直接使用处理业务,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。

  • 既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

  • 如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象(Node),通过CAS、自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。

12.6 源码查看和说明

  • AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改。

12.7 AQS同步队列的基本结构

12.8 AQS内部体系架构

12.8.1 CLH队列(三个大牛的名字组成),为一个双向队列

12.8.2 Node内部结构

12.8.3 Node属性声明

12.9 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
53
public class AQSDemo {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();//非公平锁

// A B C三个顾客,去银行办理业务,A先到,此时窗口空无一人,他优先获得办理窗口的机会,办理业务。
// A 耗时严重,估计长期占有窗口
new Thread(() -> {
reentrantLock.lock();
try {
System.out.println("----come in A");
//暂停20分钟线程
try {
TimeUnit.MINUTES.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
reentrantLock.unlock();
}
}, "A").start();

//B是第2个顾客,B一看到受理窗口被A占用,只能去候客区等待,进入AQS队列,等待着A办理完成,尝试去抢占受理窗口。
new Thread(() -> {
reentrantLock.lock();
try {
System.out.println("----come in B");
} finally {
reentrantLock.unlock();
}
}, "B").start();


//C是第3个顾客,C一看到受理窗口被A占用,只能去候客区等待,进入AQS队列,等待着A办理完成,尝试去抢占受理窗口,前面是B顾客,FIFO
new Thread(() -> {
reentrantLock.lock();
try {
System.out.println("----come in C");
} finally {
reentrantLock.unlock();
}
}, "C").start();

//后续顾客DEFG。。。。。。。以此类推
new Thread(() -> {
reentrantLock.lock();
try {
//。。。。。。
} finally {
reentrantLock.unlock();
}
}, "D").start();
}
}

12.9.1 ReentrantLock的原理

12.9.2 从最简单的lock方法开始看看公平和非公平

12.9.3 以非公平锁ReentrantLock()为例作为突破走起,方法lock()

  • 对比公平锁和非公平锁的tryAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断!hasQueuedPredecessors()。

  • hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:

  • 公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;

  • 非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程苏醒后,不一定就是排头的这个线程获得锁,它还是需要参加竞争锁(存在线程竞争的情况下),后来的线程可能不讲武德插队夺锁了。

12.9.3.1 lock()

12.9.3.2 源码和3大流程走向

12.9.3.3 本次走非公平锁

12.9.3.4 nonfairTryAcquire(acquires)

12.9.3.5 enq(node);

12.9.3.6 acquireQueued

12.9.3.7 shouldParkAfterFailedAcquire和parkAndChecklnterrupt方法中

12.9.3.8 shouldParkAfterFailedAcquire

12.9.3.9 parkAndChecklnterrupt

12.9.3.10 此时各个线程状态

12.9.4 unlock

12.9.4.1 sync.release(1);

12.9.4.2 tryRelease(arg)

12.9.4.3 unparkSuccessor(h)

12.9.4.4 此时各个线程状态