当前位置: 首页 > 科技观察

看看ReentrantLock和AQS实现原理_0

时间:2023-03-18 20:06:34 科技观察

这篇文章,面试的时候聊一个比较有杀伤力的问题:谈谈你对AQS的理解?有同学反映,去互联网公司面试的时候,面试官说并发的时候问了这个问题。那个时候,那个同学心里大概受到了10000点的伤害。..因为首先很多人真的连AQS是什么东西都不知道,可能都没听说过。或者有些人听说过AQS这个词,但他们可能连全名怎么拼都不知道。更有什者,有人会说:AQS?是一个念头吗?我们平时如何使用AQS进行开发呢?一般来说,很多同学对AQS大概都有一种模糊的感觉。如果用搜索引擎查找什么是AQS?看了几篇估计直接放弃了,因为密密麻麻的文字实在看不懂!因此,基于以上的痛点,在本文中,我们将用最简单的大白话,配上N多张手绘图,来给大家解释一下什么是AQS?让学生在面试中被问到这个问题时不要不知所措。ReentrantLock和AQS的关系首先我们来看一下。如果使用Java并发包下的ReentrantLock来加锁和释放锁,是一种什么样的感觉?基本学过Java的同学应该都知道。毕竟这是java并发基础API的使用。大家应该都学会了,那我们直接看代码吧:上面的代码应该不难理解。无非就是制作一个Lock对象,然后加锁和释放锁。这时你可能会问,这跟AQS有什么关系呢?关系越来越大了!因为Java并发包下的很多API都是基于AQS来实现加锁和释放锁等功能的。AQS是Java并发包的基础类。比如ReentrantLock和ReentrantReadWriteLock底层都是基于AQS实现的。那么AQS的全称是什么?AbstractQueuedSynchronizer,抽象队列同步器。给大家画个图。首先我们看一下ReentrantLock和AQS的关系。让我们看看上面的图片。说白了,ReentrantLock内部包含一个AQS对象,是一个AbstractQueuedSynchronizer类型的对象。这个AQS对象是ReentrantLock实现加锁和释放锁的关键核心组件。ReentrantLock加锁和释放锁的底层原理好了,那么现在如果有线程过来,尝试用ReentrantLock的lock()方法加锁,会发生什么情况呢?很简单,AQS对象内部有一个核心变量叫state,是int类型,代表加锁的状态。在初始状态下,这个状态的值为0。另外,这个AQS内部还有一个key变量,用来记录当前锁定了哪个线程。在初始化状态下,该变量为空。然后线程1跑过来调用ReentrantLock的lock()方法尝试加锁。加锁的过程就是直接使用CAS操作将状态值从0变为1。不知道CAS是什么的请看上一篇,《?Java8中的LongAdder类,大大提升CAS性能?!》。如果之前没有人加锁过,那么state的值一定是0,此时线程1可以加锁成功。一旦线程1加锁成功,就可以设置当前加锁线程为自己。所以看下图,是线程1跑过来加锁的一个进程。其实看到这里大家应该对所谓的AQS有所感触吧。说白了就是concurrent包中的一个核心组件,包含了状态变量和锁线程变量等核心的东西,维护锁状态。你会发现像ReentrantLock这样的东西只是一个外层API,内核中锁机制的实现依赖于AQS组件。这个ReentrantLock之所以以Reentrant开头,说明它是一个可重入锁。可重入锁是指可以对一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁,即可以多次加锁,称为可重入锁。了解了状态变量,你就知道如何进行可重入锁了!实际上,线程1每次可以重入加锁一次,都会判断当前加锁线程是自己,然后自己可以重入加锁多次。什么也没有变。那么,线程1加锁后线程2跑过来加锁怎么办?我们来看看锁的互斥是如何实现的?线程2跑过去一看,哎呀!state的值不为0?因此,CAS操作将状态从0变为1的过程会失败,因为状态的当前值为1,说明已经有人锁定了!然后线程2看看,是不是我之前加的锁?当然不是,变量“lockingthread”明确记录了线程1占用了锁,所以此时线程2已经加锁失败。下面放一张图给大家一起体验下这个过程:接下来线程2会把自己放到AQS中的一个等待队列中,因为它尝试锁自己失败了,所以此时会把自己放到队列中Wait,等待之后对于线程1释放锁,可以尝试再次加锁。所以可以看出AQS就是这么一个核心!AQS内部还有一个等待队列,专门用于那些加锁失败的线程!同样,这里放一张图给大家一起感受一下:那么,线程1执行完自己的业务逻辑代码后,就会释放锁!释放锁的过程非常简单。就是把AQS中的state变量的值减1,如果state值为0,锁就会被彻底释放,“锁定的线程”变量也会被置为null!整个过程见下图:接下来,线程2会从等待队列的头部唤醒,再次尝试加锁。好的!线程2现在尝试再次锁定。这个时候还是用CAS操作把状态从0变成1,这个时候就成功了。success后表示加锁成功,state会置1。另外,“加锁线程”必须设置为线程2本身,线程2本身会从等待队列中出队。最后再放一张图,一起来看看过程吧。总结OK,本文到此结束,基本上通过ReentrantLock的加锁和释放锁的过程,给大家讲解了底层依赖的AQS的核心原理。基本上这篇文章大家都看懂了,再也不用担心面试的时候被问到:说说你对AQS的理解。其实一句话,AQS就是一个并发契约的基础组件,用来实现各种锁,各种同步组件。它包括并发中的核心组件,例如状态变量、锁定线程和等待队列。