AQS底层原理连环相扣系列锁面试题分析

csdn   发布时间:2022-05-19 16:19:39   作者:Q.E.D.   我要评论
面试中,问锁主要是两方面:锁的日常使用场景 + 锁原理,锁的日常使用场景主要考察对锁 API 的使用熟练度,看看你是否真的使用过这些 API,而不是纸上谈兵,锁原理主要就是问 AQS 底层的源码原理了,接下来我们一起看一看关于锁的常见源码面试题

引导语

面试中,问锁主要是两方面:锁的日常使用场景 + 锁原理,锁的日常使用场景主要考察对锁 API 的使用熟练度,看看你是否真的使用过这些 API,而不是纸上谈兵,锁原理主要就是问 AQS 底层的源码原理了,如果问得更加深入的话,可能会现场让你实现一个简单的锁,简单要求的会让你直接使用 AQS API,复杂要求的可能需要重新实现 AQS。

1、AQS 相关面试题

1.1、说说自己对 AQS 的理解?

答:回答这样的问题的时候,面试官主要考察的是你对 AQS 的知识有没有系统的整理,建议回答的方向是由大到小,由全到细,由使用到原理。

如果和面试官面对面的话,可以边说边画出我们在 AQS 源码解析上中画出的整体架构图,并且可以这么说:

AQS 是一个锁框架,它定义了锁的实现机制,并开放出扩展的地方,让子类去实现,比如我们在 lock 的时候,AQS 开放出 state 字段,让子类可以根据 state 字段来决定是否能够获得锁,对于获取不到锁的线程 AQS 会自动进行管理,无需子类锁关心,这就是 lock 时锁的内部机制,封装的很好,又暴露出子类锁需要扩展的地方;

AQS 底层是由同步队列 + 条件队列联手组成,同步队列管理着获取不到锁的线程的排队和释放,条件队列是在一定场景下,对同步队列的补充,比如获得锁的线程从空队列中拿数据,肯定是拿不到数据的,这时候条件队列就会管理该线程,使该线程阻塞;

AQS 围绕两个队列,提供了四大场景,分别是:获得锁、释放锁、条件队列的阻塞,条件队列的唤醒,分别对应着 AQS 架构图中的四种颜色的线的走向。

以上三点都是 AQS 全局方面的描述,接着你可以问问面试官要不要说细一点,可以的话,按照 AQS 源码解析上下两篇,把四大场景都说一下就好了。

这样说的好处是很多的:

面试的主动权把握在自己手里,而且都是自己掌握的知识点;

由全到细的把 AQS 全部说完,会给面试官一种你对 AQS 了如指掌的感觉,再加上全部说完耗时会很久,面试时间又很有限,面试官就不会再问关于 AQS 一些刁钻的问题了,这样 AQS 就可以轻松过关。

当然如果你对 AQS 了解的不是很深,那么就大概回答下 AQS 的大体架构就好了,就不要说的特别细,免得给自己挖坑。

1.2、多个线程通过锁请求共享资源,获取不到锁的线程怎么办?

答:加锁(排它锁)主要分为以下四步:

尝试获得锁,获得锁了直接返回,获取不到锁的走到 2;

用 Node 封装当前线程,追加到同步队列的队尾,追加到队尾时,又有两步,如 3 和 4;

自旋 + CAS 保证前一个节点的状态置为 signal;

阻塞自己,使当前线程进入等待状态。

获取不到锁的线程会进行 2、3、4 步,最终会陷入等待状态,这个描述的是排它锁。

1.3、问题 1.2 中,排它锁和共享锁的处理机制是一样的么?

答:排它锁和共享锁在问题 1.2 中的 2、3、4 步骤都是一样的, 不同的是在于第一步,线程获得排它锁的时候,仅仅把自己设置为同步队列的头节点即可,但如果是共享锁的话,还会去唤醒自己的后续节点,一起来获得该锁。

1.4、共享锁和排它锁的区别?

答:排它锁的意思是同一时刻,只能有一个线程可以获得锁,也只能有一个线程可以释放锁。

共享锁可以允许多个线程获得同一个锁,并且可以设置获取锁的线程数量,共享锁之所以能够做到这些,是因为线程一旦获得共享锁,把自己设置成同步队列的头节点后,会自动的去释放头节点后等待获取共享锁的节点,让这些等待节点也一起来获得共享锁,而排它锁就不会这么干。

1.5、排它锁和共享锁说的是加锁时的策略,那么锁释放时有排它锁和共享锁的策略么?

答:是的,排它锁和共享锁,主要体现在加锁时,多个线程能否获得同一个锁。

但在锁释放时,是没有排它锁和共享锁的概念和策略的,概念仅仅针对锁获取。

1.6、描述下同步队列?

答:同步队列底层的数据结构就是双向的链表,节点叫做 Node,头节点叫做 head,尾节点叫做 tail,节点和节点间的前后指向分别叫做 prev、next,如果是面对面面试的话,可以画一下 AQS 整体架构图中的同步队列。

同步队列的作用:阻塞获取不到锁的线程,并在适当时机释放这些线程。

实现的大致过程:当多个线程都来请求锁时,某一时刻有且只有一个线程能够获得锁(排它锁),那么剩余获取不到锁的线程,都会到同步队列中去排队并阻塞自己,当有线程主动释放锁时,就会从同步队列中头节点开始释放一个排队的线程,让线程重新去竞争锁。

1.7、描述下线程入、出同步队列的时机和过程?

答:(排它锁为例)从 AQS 整体架构图中,可以看出同步队列入队和出队都是有两个箭头指向,所以入队和出队的时机各有两个。

同步队列入队时机:

多个线程请求锁,获取不到锁的线程需要到同步队列中排队阻塞;

条件队列中的节点被唤醒,会从条件队列中转移到同步队列中来。

同步队列出队时机:

锁释放时,头节点出队;

获得锁的线程,进入条件队列时,会释放锁,同步队列头节点开始竞争锁。

四个时机的过程可以参考 AQS 源码解析,1 参考 acquire 方法执行过程,2 参考 signal 方法,3 参考 release 方法,4 参考 await 方法。

1.8、为什么 AQS 有了同步队列之后,还需要条件队列?

答:的确,一般情况下,我们只需要有同步队列就好了,但在上锁后,需要操作队列的场景下,一个同步队列就搞不定了,需要条件队列进行功能补充,比如当队列满时,执行 put 操作的线程会进入条件队列等待,当队列空时,执行 take 操作的线程也会进入条件队列中等待,从一定程度上来看,条件队列是对同步队列的场景功能补充。

1.9、描述一下条件队列中的元素入队和出队的时机和过程?

答:

入队时机:执行 await 方法时,当前线程会释放锁,并进入到条件队列。

出队时机:执行 signal、signalAll 方法时,节点会从条件队列中转移到同步队列中。

具体的执行过程,可以参考源码解析中 await 和 signal 方法。

1.10、描述一下条件队列中的节点转移到同步队列中去的时机和过程?

答:时机:当有线程执行 signal、signalAll 方法时,从条件队列的头节点开始,转移到同步队列中去。

过程主要是以下几步:

找到条件队列的头节点,头节点 next 属性置为 null,从条件队列中移除了;

头节点追加到同步队列的队尾;

头节点状态(waitStatus)从 CONDITION 修改成 0(初始化状态);

将节点的前一个节点状态置为 SIGNAL。

1.11、线程入条件队列时,为什么需要释放持有的锁?

答:原因很简单,如果当前线程不释放锁,一旦跑去条件队里中阻塞了,后续所有的线程都无法获得锁,正确的场景应该是:当前线程释放锁,到条件队列中去阻塞后,其他线程仍然可以获得当前锁。

2、AQS 子类锁面试题

2.1、你在工作中如何使用锁的,写一个看一看?

答:这个照实说就好了

2.2、如果我要自定义锁,大概的实现思路是什么样子的?

答:现在有很多类似的问题,比如让你自定义队列,自定义锁等等,面试官其实并不是想让我们重新造一个轮子,而是想考察一下我们对于队列、锁理解的深度,我们只需要选择自己最熟悉的 API 描述一下就好了,所以这题我们可以选择 ReentrantLock 来描述一下实现思路:

新建内部类继承 AQS,并实现 AQS 的 tryAcquire 和 tryRelease 两个方法,在 tryAcquire 方法里面实现控制能否获取锁,比如当同步器状态 state 是 0 时,即可获得锁,在 tryRelease 方法里面控制能否释放锁,比如将同步器状态递减到 0 时,即可释放锁;

对外提供 lock、release 两个方法,lock 表示获得锁的方法,底层调用 AQS 的 acquire 方法,release 表示释放锁的方法,底层调用 AQS 的 release 方法。

2.3、描述 ReentrantLock 两大特性:可重入性和公平性?底层分别如何实现的?

答:可重入性说的是线程可以对共享资源重复加锁,对应的,释放时也可以重复释放,对于 ReentrantLock 来说,在获得锁的时候,state 会加 1,重复获得锁时,不断的对 state 进行递增即可,比如目前 state 是 4,表示线程已经对共享资源加锁了 4 次,线程每次释放共享资源的锁时,state 就会递减 1,直到递减到 0 时,才算真正释放掉共享资源。

公平性和非公平指的是同步队列中的线程得到锁的机制,如果同步队列中的线程按照阻塞的顺序得到锁,我们称之为公平的,反之是非公平的,公平的底层实现是 ReentrantLock 的 tryAcquire 方法(调用的是 AQS 的 hasQueuedPredecessors 方法)里面实现的,要释放同步队列的节点时(或者获得锁时),判断当前线程节点是不是同步队列的头节点的后一个节点,如果是就释放,不是则不能释放,通过这种机制,保证同步队列中的线程得到锁时,是按照从头到尾的顺序的。

2.4、如果一个线程需要等待一组线程全部执行完之后再继续执行,有什么好的办法么?是如何实现的?

答:CountDownLatch 就提供了这样的机制,比如一组线程有 5 个,只需要在初始化 CountDownLatch 时,给同步器的 state 赋值为 5,主线程执行 CountDownLatch.await ,子线程都执行 CountDownLatch.countDown 即可。

2.5、Atomic 原子操作类可以保证线程安全,如果操作的对象是自定义的类的话,要如何做呢?

答: Java 为这种情况提供了一个 API:AtomicReference,AtomicReference 类可操作的对象是个泛型,所以支持自定义类。

3、总结

关于 AQS 和锁场景的面试题,其实网上也很多,各个大厂出的题目也都不一样,但考察问题的本质都是一致的,如果把 AQS 架构图中,AQS 的组成和四种颜色箭头的发起时机,调用过程都弄清楚了,回答 AQS 的各种问题都会游刃有余。

以上就是AQS底层原理连环相扣系列锁面试题分析的详细内容,更多关于AQS原理连环相扣系列锁面试分析的资料请关注脚本之家其它相关文章!

相关文章

最新评论