【JUC源码】关于AQS的几个问题
1.说说你对 AQS 的理解?
答:回答的方向是由大到小,由全到细,由使用到原理。
AQS 是一个锁框架,它定义了锁的实现机制,并开放出扩展的地方,让子类去实现,比如我们在 lock 的时候,AQS 开放出 state 字段,让子类可以根据 state 字段来决定是否能够获得锁,对于获取不到锁的线程 AQS 会自动进行管理,无需子类锁关心,这就是 lock 时锁的内部机制,封装的很好,又暴露出子类锁需要扩展的地方;
AQS 底层是由同步队列 + 条件队列联手组成,同步队列管理着获取不到锁的线程的排队和释放,条件队列是在一定场景下,对同步队列的补充,比如获得锁的线程从空队列中拿数据,肯定是拿不到数据的,这时候条件队列就会管理该线程,使该线程阻塞;
AQS 围绕两个队列,提供了四大场景,分别是:获得锁、释放锁、条件队列的阻塞,条件队列的唤醒。具体的内容请参考上篇 AQS 源码分析。
2.多个线程通过锁请求共享资源,获取不到锁的线程怎么办?
答:加锁(排它锁)主要分为以下四步:
尝试获得锁,获得锁了直接返回,获取不到锁的走到 2;
用 Node 封装当前线程,追加到同步队列的队尾,追加到队尾时,又有两步,如 3 和 4;
自旋 + CAS 保证前一个节点的状态置为 signal;
阻塞自己,使当前线程进入等待状态。
获取不到锁的线程会进行 2、3、4 步,最终会陷入等待状态,这个描述的是排它锁。
3.上一问题中,排它锁和共享锁的处理机制是一样的么?
答:排它锁和共享锁在问题2中的 2、3、4 步骤都是一样的, 不同的是在于第一步,线程获得排它锁的时候,仅仅把自己设置为同步队列的头节点即可,但如果是共享锁的话,还会去唤醒自己的后续节点,一起来获得该锁
4.共享锁和排它锁的区别?
答:排它锁的意思是同一时刻,只能有一个线程可以获得锁,也只能有一个线程可以释放锁。
共享锁可以允许多个线程获得同一个锁,并且可以设置获取锁的线程数量,共享锁之所以能够做到这些,是因为线程一旦获得共享锁,把自己设置成同步队列的头节点后,会自动的去释放头节点后等待获取共享锁的节点,让这些等待节点也一起来获得共享锁,而排它锁就不会这么干。
5.排它锁和共享锁说的是加锁时的策略,那么锁释放时有排它锁和共享锁的策略么?
答:是的,排它锁和共享锁,主要体现在加锁时,多个线程能否获得同一个锁。但在锁释放时,是没有排它锁和共享锁的概念和策略的,概念仅仅针对锁获取。
6.描述下同步队列?
答:同步队列底层的数据结构就是双向链表,节点叫做 Node,头节点叫做 head,尾节点叫做 tail,节点和节点间的前后指向分别叫做 prev、next,如果是面对面面试的话,可以画一下 AQS 整体架构图中的同步队列。
同步队列的作用:阻塞获取不到锁的线程,并在适当时机释放这些线程。
实现的大致过程:当多个线程都来请求锁时,某一时刻有且只有一个线程能够获得锁(排它锁),那么剩余获取不到锁的线程,都会到同步队列中去排队并阻塞自己,当有线程主动释放锁时,就会从同步队列中头节点开始释放一个排队的线程,让线程重新去竞争锁。
7.线程入、出同步队列的时机和过程?
答:(排它锁为例)入队和出队的时机各有两个
同步队列入队时机:多个线程请求锁,获取不到锁的线程需要到同步队列中排队阻塞;
条件队列中的节点被唤醒,会从条件队列中转移到同步队列中来。
同步队列出队时机:锁释放时,头节点出队;
获得锁的线程,进入条件队列时,会释放锁,同步队列头节点开始竞争锁。
四个时机的过程可以参考AQS 源码分析,1 参考 acquire 方法执行过程,2 参考 signal 方法,3 参考 release 方法,4 参考 await 方法。
8.为什么 AQS 有了同步队列之后,还需要条件队列?
答:一般情况下,我们只需要有同步队列就好了,但在上锁后,需要操作队列的场景下,一个同步队列就搞不定了,需要条件队列进行功能补充,比如当队列满时,执行 put 操作的线程会进入条件队列等待,当队列空时,执行 take 操作的线程也会进入条件队列中等待,从一定程度上来看,条件队列是对同步队列的场景功能补充。
9.条件队列中的元素入队和出队的时机和过程?
答:入队时机:执行 await 方法时,当前线程会释放锁,并进入到条件队列。
出队时机:执行 signal、signalAll 方法时,节点会从条件队列中转移到同步队列中。
具体的执行过程,可以参考AQS 源码分析中 await 和 signal 方法。
10.条件队列中的节点转移到同步队列中去的时机和过程?
答:时机:当有线程执行 signal、signalAll 方法时,从条件队列的头节点开始,转移到同步队列中去。过程主要是以下几步:
找到条件队列的头节点,头节点 next 属性置为 null,从条件队列中移除了;
头节点追加到同步队列的队尾;
头节点状态(waitStatus)从 CONDITION 修改成 0(初始化状态);
将节点的前一个节点状态置为 SIGNAL。
11.线程入条件队列时,为什么需要释放持有的锁?
答:原因很简单,如果当前线程不释放锁,一旦跑去条件队里中阻塞了,后续所有的线程都无法获得锁,正确的场景应该是:当前线程释放锁,到条件队列中去阻塞后,其他线程仍然可以获得当前锁。
作者:A minor
来源:CSDN