༺歲月蹉跎༻

只要路是对的,就不怕路远!

0%

AbstractQueuedSynchronizer之AQS

12、AbstractQueuedSynchronizer之AQS

12.1 AQS源代码

1654827418889

12.2 官网解释

1654827439226

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

1654827855339

12.4 和AQS有关的

1654828008061

12.4.1 ReentrantLock

1654828032453

12.4.2 CountDownLatch

1654828046411

12.4.3 ReentrantReadWriteLock

1654828059417

12.4.4 Semaphore

1654828072473

12.5 解释说明

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

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

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

    1654828282015

12.6 源码查看和说明

1654828297258

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

    1654828317128

12.7 AQS同步队列的基本结构

1654828333573

12.8 AQS内部体系架构

1654828395091

1654828399749

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

1654828433269

12.8.2 Node内部结构

1654828460670

1654828464265

1654828468216

12.8.3 Node属性声明

1654828489363

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的原理

1654828546619

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

1654828593075

1654828596403

1654828599444

1654828603836

1654828606605

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

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

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

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

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

    1654828897290

    1654828908627

12.9.3.1 lock()

1654828930340

12.9.3.2 源码和3大流程走向

1654828948266

16548289538831654828972308

12.9.3.3 本次走非公平锁

1654829003849

12.9.3.4 nonfairTryAcquire(acquires)

1654829020193

12.9.3.5 enq(node);

1654829045330

12.9.3.6 acquireQueued

1654829064506

1654829069384

12.9.3.7 shouldParkAfterFailedAcquire和parkAndChecklnterrupt方法中

1654829086834

12.9.3.8 shouldParkAfterFailedAcquire

1654829102259

12.9.3.9 parkAndChecklnterrupt

1654829117998

12.9.3.10 此时各个线程状态

1654829195158

12.9.4 unlock

1654829214189

12.9.4.1 sync.release(1);

1654829232721

12.9.4.2 tryRelease(arg)

1654830091515

12.9.4.3 unparkSuccessor(h)

1654830105868

12.9.4.4 此时各个线程状态

1654830255143