可重入锁又名递归锁,是指同一个线程 在外层方法获取锁的时候,再进入该线程的内层方法时,会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。(通俗来讲就是,洋葱最外面加了一把锁,里面每一层都是和最外面的锁一样,那么当我得到最外面的一把锁时,我就可以直接深入到洋葱的内心;如果最外面是一把,里面的每一层都不相同,那么我就卡在最外面了)
Java中ReentrantLock 和 synchronized 都是可重入锁,可重入锁的一个优点是一定程度避免死锁。
可重入锁的字面解释
可:可以。
重:再次。
入:进入——进入什么?进入同步域(即同步代码块/方法或显式锁 锁定的代码)
锁:同步锁
也就是说:一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。即自己可以获取自己的内部锁
即synchronized关键字使用的锁,默认是可重入锁(像自动档,由JVM控制)
1、同步代码块
public class ReEnterLockDemo {
// synchronized 同步代码块可重入演示
static Object objectLockA = new Object();
public static void m1() {
new Thread(() -> {
synchronized (objectLockA) {
System.out.println(Thread.currentThread().getName() + "\t" + "------外层调用");
synchronized (objectLockA) {
System.out.println(Thread.currentThread().getName() + "\t" + "------中层调用");
synchronized (objectLockA) {
System.out.println(Thread.currentThread().getName() + "\t" + "------内层调用");
}
}
}
}, "t1").start();
}
public static void main(String[] args) {
m1();
}
}
程序运行结果:在同一个线程可以多次获取同一把锁
2、同步方法
public class ReEnterLockDemo {
// synchronized 同步方法可重入演示
public synchronized void m1() {
System.out.println("=====外层");
m2();
}
public synchronized void m2() {
System.out.println("=====中层");
m3();
}
public synchronized void m3() {
System.out.println("=====内层");
}
public static void main(String[] args) {
new ReEnterLockDemo().m1();
}
}
1.在 IDEA 终端中进入包名所在的文件夹
2.使用 javap -c xxx.class 指令反编译字节码文件,可以看到有一对配对出现的 monitorenter 和 monitorexit 指令,一个对应于加锁,一个对应于解锁
为什么会多出来一个 monitorexit 指令呢?
如果同步代码块中出现Exception或者Error,则会调用 第二个monitorexit指令 来保证释放锁
小结 :有点像PV操作
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程指针。
当执行monitorenter时,如果目标锋对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
即Lock,也有ReentrantLock这样的可重入锁。(就像手动挡,要自己写)
代码示例一:可重入演示
public class ReEnterLockDemo {
// ReentrantLock 可重入演示
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println("=======外层");
lock.lock();
try {
System.out.println("=======内层");
} finally {
lock.unlock(); //正常情况,加锁几次就要解锁几次
}
} finally {
lock.unlock(); //正常情况,加锁几次就要解锁几次
}
}, "t1").start();
}
}
程序运行结果:在同一个线程内部成功获取同一把锁
代码示例二:加锁几次就要解锁几次
错误示例:
public class ReEnterLockDemo {
// ReentrantLock 可重入演示
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println("=======外层");
lock.lock();
try {
System.out.println("=======内层");
} finally {
lock.unlock();
}
} finally {
//实现加锁次数和释放次数不一样
//由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
//lock.unlock(); //正常情况,加锁几次就要解锁几次,这里不解锁
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
System.out.println("b thread----外层调用lock");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
}
程序运行结果:执行到 t2 线程卡死,这是因为 t1 线程加了两次锁,但是之释放了一次锁,因此 t2 线程拿不到锁,程序无法正常结束
正确示例:
public class ReEnterLockDemo {
// ReentrantLock 可重入演示
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println("=======外层");
lock.lock();
try {
System.out.println("=======内层");
} finally {
lock.unlock();
}
} finally {
//实现加锁次数和释放次数不一样
//由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
lock.unlock(); //正常情况,加锁几次就要解锁几次
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
System.out.println("b thread----外层调用lock");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
}
程序运行结果:t1 线程加了两次锁,释放了两次锁,t2 线程可以拿到锁,等到 t2 线程执行完后,程序结束
LockSupport类是用来 创建锁和其他同步类 的基本线程阻塞原语。
LockSupport中的park()是阻塞线程 unpark()的作用是解除阻塞线程,因此我们可以将其看作是线程等待唤醒机制(wait/notify)的加强版
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
1、正常情况:实现线程的等待和唤醒
static Object objectLock = new Object();
private static void synchronizedWaitNotify() {
new Thread(() -> {
synchronized (objectLock) {
System.out.println(Thread.currentThread().getName() + "\t" + "------come in");
try {
objectLock.wait(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒");
}
}, "A").start();
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify(); // 唤醒
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
}
}, "B").start();
}
程序运行结果:A 线程先执行,执行 objectLock.wait() 后被阻塞,B 线程在 A 线程之后执行 objectLock.notify() 将 A线程唤醒
2、异常情况一:不在 synchronized 关键字中使用 wait() 和 notify() 方法
static Object objectLock = new Object();
private static void synchronizedWaitNotify() {
new Thread(() -> {
//synchronized (objectLock) {
System.out.println(Thread.currentThread().getName() + "\t" + "------come in");
try {
objectLock.wait(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒");
//}
}, "A").start();
new Thread(() -> {
//synchronized (objectLock) {
objectLock.notify(); // 唤醒
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
//}
}, "B").start();
}
程序运行结果:不在 synchronized 关键字中使用 wait() 和 notify() 方法 ,将抛出 java.lang.IllegalMonitorStateException 异常
3、异常情况二:先 notify() 后 wait()
static Object objectLock = new Object();
private static void synchronizedWaitNotify() {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3L);//目的是为了让B线程先走
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectLock) {
System.out.println(Thread.currentThread().getName() + "\t" + "------come in");
try {
objectLock.wait(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒");
}
}, "A").start();
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify(); // 唤醒
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
}
}, "B").start();
}
程序运行结果:B 线程先执行 objectLock.notify(),A 线程再执行 objectLock.wait(),这样 A 线程无法被唤醒
小结
1、正常情况:实现线程的等待和唤醒
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
private static void lockAwaitSignal() {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + "------come in");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒");
} finally {
lock.unlock();
}
}, "A").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
} finally {
lock.unlock();
}
}, "B").start();
}
程序运行结果:A 线程先执行,执行 condition.await() 后被阻塞,B 线程在 A 线程之后执行 condition.signal() 将 A线程唤醒
2、异常情况一:不在 lock() 和 unlock() 方法内使用 await() 和 signal() 方法
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
private static void lockAwaitSignal() {
new Thread(() -> {
//lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + "------come in");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒");
} finally {
//lock.unlock();
}
}, "A").start();
new Thread(() -> {
//lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
} finally {
//lock.unlock();
}
}, "B").start();
}
程序运行结果:不在 lock() 和 unlock() 方法内使用 await() 和 signal() 方法,将抛出 java.lang.IllegalMonitorStateException 异常
3、异常情况二:先 signal() 后 await()
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
private static void lockAwaitSignal() {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3L);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + "------come in");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒");
} finally {
lock.unlock();
}
}, "A").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
} finally {
lock.unlock();
}
}, "B").start();
}
程序运行结果:B 线程先执行 condition.signal(),A 线程再执行 condition.await(),这样 A 线程无法被唤醒
通过以上两种方式,我们可以知道:
传统的 synchronized 和 Lock 实现等待唤醒通知的约束
线程先要获得并持有锁,必须在锁块(synchronized或lock)中
必须要先等待后唤醒,线程才能够被唤醒
LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport 类使用了一种名为 permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit 只有两个值 1 和零,默认是零。
可以把许可看成是一种(0, 1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是 1。
也就是说:每个线程都带有一个许可证,默认值为0(阻塞),如果你现在没有许可证,就不允许放行。由谁来发放这个许可证呢?就是我们的unpark方法。
阻塞
park() / park(Object blocker)
park() 方法的作用:阻塞当前线程 / 阻塞传入的具体线程
permit 默认是 0,所以一开始调用 park() 方法,当前线程就会阻塞(不允许放行),直到别的线程将当前线程的 permit 设置为 1 时,park() 方法会被唤醒(放行),然后会将 permit 再次设置为 0 并返回。
park() 方法通过 Unsafe 类实现
// Disables the current thread for thread scheduling purposes unless the permit is available.
public static void park() {
UNSAFE.park(false, 0L);
}
唤醒
unpark(Thread thread)
unpark() 方法的作用:唤醒处于阻断状态的指定线程
调用 unpark(thread) 方法后,就会将 thread 线程的许可 permit 设置成 1(注意多次调用 unpark()方法,不会累加,permit 值还是 1),这会自动唤醒 thread 线程,即之前阻塞中的LockSupport.park()方法会立即返回。
unpark() 方法通过 Unsafe 类实现
// Makes available the permit for the given thread
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
1、正常使用 LockSupport
private static void lockSupportParkUnpark() {
Thread a = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "------come in");
LockSupport.park(); // 线程 A 阻塞
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒");
}, "A");
a.start();
new Thread(() -> {
LockSupport.unpark(a); // B 线程唤醒线程 A
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
}, "B").start();
}
程序运行结果:A 线程先执行 LockSupport.park() 方法将通行证(permit)设置为 0,其实这并没有什么鸟用,因为 permit 初始值本来就为 0,然后 B 线程执行 LockSupport.unpark(a) 方法将 permit 设置为 1,此时 A 线程可以通行(完全不需要synchronized和lock)
2、先 unpark() 后 park()
private static void lockSupportParkUnpark() {
Thread a = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3L);//为了先执行线程B中的unpark
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "------come in" + System.currentTimeMillis());
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒" + System.currentTimeMillis());
}, "A");
a.start();
new Thread(() -> {
LockSupport.unpark(a);
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
}, "B").start();
}
程序运行结果:因为引入了通行证的概念,所以先唤醒(unpark())其实并不会有什么影响,从程序运行结果可以看出,A 线程执行 LockSupport.park() 时并没有被阻塞(尾号一样,说明LockSupport.park()形同虚设,没有起到阻塞的作用),也就是说,在LockSupport 中 unpark 可以在 park 前执行
3、异常情况:没有考虑到 permit 上限值为 1
private static void lockSupportParkUnpark() {
Thread a = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "------come in" + System.currentTimeMillis());
LockSupport.park();
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒" + System.currentTimeMillis());
}, "A");
a.start();
new Thread(() -> {
LockSupport.unpark(a);
LockSupport.unpark(a); //虽然unpark两次,但是只有一个证,一个证对应一个park,所以第二个park会被阻塞
System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
}, "B").start();
}
程序运行结果:由于 permit 的上限值为 1,所以执行两次 LockSupport.park() 操作将导致 A 线程阻塞
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit,也就是将1变成0,同时park立即返回。
如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证。
形象的理解
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
当调用park方法时
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。
以前的两种方式:
这上面这两个都必须要持有锁才能干
LockSupport:俗称锁中断,LockSupport 解决了 synchronized 和 lock 的痛点
LockSupport不用持有锁块,不用加锁,程序性能好,无须注意唤醒和阻塞的先后顺序,不容易导致卡死
LockSupport 相关问题
为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。
前置知识
公平锁和非公平锁
可重入锁
LockSupport
自旋锁——CAS
数据结构之链表
设计模式之模板设计模式
概念
AbstractQueuedSynchronizer之AQS:抽象的队列同步器(抽象说明是抽象类,队列表示线程抢不到锁就要排队)
一般我们说的 AQS 指的是 java.util.concurrent.locks 包下的 AbstractQueuedSynchronizer
但其实共有三种抽象队列同步器:AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer 和 AbstractQueuedLongSynchronizer
AQS 是用来构建锁或者其它同步器组件的 重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态
我们可以粗略的认为,AQS就是一个 state资源(int变量)+ CLH队列(FIFO队列)
CLH:Craig、Landin and Hagersten 队列,默认是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO
以下几个内容都和AQS有关
进一步理解锁和同步器的关系
加锁会导致阻塞
有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
抢到资源的线程直接使用办理业务,抢占不到资源的线程的必然涉及一种排队等候机制,抢占资源失败的线程继续去等待(类似办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
既然说到了排队等候机制,那么就一定 会有某种队列形成,这样的队列是什么数据结构呢?
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。(这里的共享资源相当于银行办理窗口,客户就相当于一个个线程,锁就是叫号的广播,叫号广播系统就是阻塞等待唤醒机制)。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node) ,通过CAS、自旋以及LockSuport.park()的方式,维护state变量的状态,使并发达到同步的效果。
1.AQS如何管理thread
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的 FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成 一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
AQS虽然是管理Thread的,但是我们不是直接将Thread扔到AQS里面,而是先把thread放到node节点里面,再把node放到AQS里面。
Node 节点是啥
类似于 HashMap 的 Node 节点,JDK 用 static class Node
下面这张图只是我的猜测:(貌似猜对了)——node节点中还有一个waitstate变量,本图没表示
2.AQS内部体系架构
AQS的int变量
AQS的同步状态State成员变量,类似于银行办理业务的受理窗口状态:零就是没人,自由状态可以办理;大于等于1,有人占用窗口,等着去
AQS的CLH队列
CLH队列(三个大牛的名字组成),为一个双向队列,类似于银行侯客区的等待顾客
综上:AQS= state + CLH变体的双向队列
AQS的内部类Node(Node类在AQS类内部)
Node的int变量
Node的等待状态waitState成员变量,表示当前结点在队列中的状态,类似于等候区其它顾客(其它线程)的等待状态,队列中每个排队的个体就是一个Node
首先我们来看一张类图,由图可知:
ReentrantLock 实现了 Lock 接口,在 ReentrantLock 内部聚合了一个Sync类,Sync类实现了AbstractQueuedSynchronizer接口。
下面我们通过源码来理解这张图。
ReentrantLock 默认是创建非公平锁,源码如下:
public ReentrantLock() {
sync = new NonfairSync();
}
ReentrantLock有参构造,源码如下:(传入为true则是公平锁,传入false则为非公平锁)
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
下面我们来看公平锁 和 非公平锁 的源码,可以得出:NoFairSync 和 FairSync 中 tryAcquire() 方法的区别,可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件: hasQueuedPredecessors()
hasQueuedPredecessors() 方法是公平锁加锁时判断等待队列中是否存在有效节点的方法
对比 公平锁 和 非公平锁 的tryAcqure()方法的实现代码, 其实差别就在于 非公平锁 获取锁时 比公平锁中少了一个判断 !hasQueuedPredecessors(),其他代码都长得一模一样。
hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:
下面我们来走一遍流程:
而 acquire() 方法最终都会调用 tryAcquire() 方法
没错我还是很懵逼!那么就来举个栗子吧…
下面我们将带入一个银行办理业务的案例,来模拟我们的AQS如何进行 线程的管理 和 通知唤醒机制。
3个线程模拟来银行办理业务的顾客,银行柜台小姐姐只有一名
public class AQSDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
// A线程就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
new Thread(() -> {
lock.lock();
try {
System.out.println("-----A thread come in");
try {
TimeUnit.MINUTES.sleep(20); //办理个20min
} catch (Exception e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}, "A").start();
// 第二个顾客,第二个线程,由于受理业务的小姐姐只有一个(只能一个线程持有锁),此时B只能等待,
// 进入候客区
new Thread(() -> {
lock.lock();
try {
System.out.println("-----B thread come in");
} finally {
lock.unlock();
}
}, "B").start();
// 第三个顾客,第三个线程,由于受理业务的小姐姐只有一个(只能一个线程持有锁),此时C只能等待,
// 进入候客区
new Thread(() -> {
lock.lock();
try {
System.out.println("-----C thread come in");
} finally {
lock.unlock();
}
}, "C").start();
}
}
先来看看顾客A
顾客A是第一个来的,没有人和他抢
A的流程如下:
下面分析 final void lock() 方法
之前已经讲到过,new ReentrantLock() 不传参默认是非公平锁,调用 lock.lock() 方法最终都会执行 NonfairSync 重写后的 lock() 方法.
由于第一次执行 lock() 方法,state 变量的值等于 0,表示 lock 锁没有被占用,此时执行 compareAndSetState(0, 1) CAS 判断,可得 state = = expected== 0,因此 CAS 成功,将 state 的值修改为 1
再来看看顾客B
他先A的流程一样,先到窗口去看看有没有自己的位置,发现state=1,有人占用了
下面对 final boolean nonfairTryAcquire(int acquires) 进行分析
该方法继承了tryAcquire(arq)
没办法,b只能乖乖去排队了,源码流程如下:
源码流程图对应的动画如下:
最后来看看顾客C
他和B一样,不能占用窗口,只能乖乖去排队
看完了上面三个流程,总算不晕了(强烈建议不要光看图,自己去idea里面走一边源码流程,效果更佳!!)
注意
双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个 有数据的节点,是从第二个节点开始的。
你以为这就结束了?并没有!还剩最后一个方法
final boolean acquireQueued(final Node node, int arg)
这个方法是干什么呢?前面的流程只是告诉线程B、C,你们两个要去排队了(那个队伍就是傀儡结点打头的),但是线程B、C还没有安安稳稳的坐在队伍里面呢!所以 acquireQueued 方法就是让 线程B、C 安安稳稳坐在队伍里(也就是把他们真正的阻塞)。
线程B、C总不能一直阻塞吧…所以需要被唤醒,怎么被唤醒呢?接着来看!
下面是线程A的操作,他通过 LockSupport.unpark 方法唤醒 线程B
这个线程B就可以杀回马枪占用窗口了
线程B占用窗口的时候,把自己的位置作为新的傀儡结点。
以上,我们通过 ReentrantLock 来分析 AQS源码,走的是非公平锁这条路。